Ga naar hoofdinhoud

Leaf integration system

Every "linked thing" on an Open Register object — a meeting, a contact, a chat room, a wiki page — is a leaf. Each leaf implements the same provider contract on the backend and registers the same way on the frontend. The result: one sidebar tab pattern, one widget pattern, one admin page that lists every leaf with its health status. Apps install only the leaves they need; the rest stay hidden.

This page documents the system. Each leaf has its own page under Integrations.

What a leaf gives you

  • A sidebar tab on every object detail page. One tab per registered leaf, filtered to the apps the user has installed.
  • A dashboard widget in four surfaces: user-dashboard, app-dashboard, detail-page, single-entity. The same registration drives all four.
  • A reference-property renderer. A schema property typed as referenceType: '<leaf-id>' renders the linked entity inline in CnFormDialog and CnDetailGrid.
  • An admin row under Administration → Open Register → Integrations. Reports install state, auth status, storage strategy, and a deep-link to configure the upstream source.
  • An OCS capability entry under openregister.integrations.providers. Clients discover the registry surface without probing routes.

All of this for one provider class and one registry descriptor per leaf.

The 18 leaves Open Register ships

Files
files
Built-in

Files attached to an object. Always available.

Group
Core
Required app
None (always available)
Storage
Magic column
Icon
Paperclip
Notes
notes
Built-in

Free-form notes on an object. Always available.

Group
Core
Required app
None (always available)
Storage
Link table
Icon
CommentTextOutline
Tags
tags
Built-in

System tags on an object. Always available.

Group
Core
Required app
None (always available)
Storage
Link table
Icon
TagOutline
Tasks
tasks
Built-in

To-do items on an object. Always available.

Group
Core
Required app
None (always available)
Storage
Link table
Icon
CheckboxMarkedOutline
Audit trail
audit-trail
Built-in

Every change to an object. Read-only, always available.

Group
Core
Required app
None (always available)
Storage
Query-time (live)
Icon
History
Shares
shares
Backend ready

NC Share Manager-backed share visibility per object.

Group
Core
Required app
None (always available)
Storage
Query-time (live)
Icon
Share
Meetings
calendar
Backend ready

CalDAV meetings linked to an object.

Group
Communication
Required app
calendar
Storage
Link table
Icon
Calendar
Contacts
contacts
Backend ready

vCard contacts linked to an object, with role.

Group
Communication
Required app
contacts
Storage
Link table
Icon
AccountBox
Emails
email
Backend ready

Link existing NC Mail messages to an object.

Group
Communication
Required app
mail
Storage
Link table
Icon
Email
Chat
talk
Provider stub

Talk conversations linked to an object. Provider stub.

Group
Communication
Required app
spreed
Storage
Link table
Icon
ChatOutline
Bookmarks
bookmarks
Provider stub

NC Bookmarks linked to an object. Provider stub.

Group
Documents
Required app
bookmarks
Storage
Link table
Icon
Bookmark
Knowledge
collectives
Provider stub

Collectives pages linked to an object. Provider stub.

Group
Documents
Required app
collectives
Storage
Link table
Icon
BookOpenPageVariant
Location
maps
Provider stub

NC Maps locations linked to an object. Provider stub.

Group
Documents
Required app
maps
Storage
Link table
Icon
MapMarker
Photos
photos
Provider stub

NC Photos linked to an object with EXIF metadata. Provider stub.

Group
Documents
Required app
photos
Storage
Link table
Icon
Image
Activity
activity
Provider stub

NC Activity events relevant to an object. Read-only stub.

Group
Workflow
Required app
activity
Storage
Query-time (live)
Icon
Timeline
Analytics
analytics
Provider stub

NC Analytics reports linked to an object. Provider stub.

Group
Workflow
Required app
analytics
Storage
Link table
Icon
ChartBar
Costs
cospend
Provider stub

NC Cospend projects/bills linked to an object. Provider stub.

Group
Workflow
Required app
cospend
Storage
Link table
Icon
CurrencyEur
Cards
deck
Backend ready

NC Deck cards linked to or created from an object.

Group
Workflow
Required app
deck
Storage
Link table
Icon
ViewColumnOutline
Automation
flow
Provider stub

NC Flow rules scoped to a schema/object. Provider stub.

Group
Workflow
Required app
workflowengine
Storage
Link table
Icon
RobotOutline
Forms
forms
Provider stub

NC Forms responses linked to an object. Provider stub.

Group
Workflow
Required app
forms
Storage
Link table
Icon
ClipboardText
Polls
polls
Provider stub

NC Polls linked to an object. Provider stub.

Group
Workflow
Required app
polls
Storage
Link table
Icon
Poll
Time
time-tracker
Provider stub

NC time tracking entries linked to an object. Provider stub.

Group
Workflow
Required app
timemanager
Storage
Link table
Icon
Clock
Articles
xwiki
External (OpenConnector)

XWiki pages linked to an object. Routed through OpenConnector.

Group
External
Required app
openconnector
Storage
External (no local store)
Icon
FileDocumentMultiple
Projects
openproject
External (OpenConnector)

OpenProject work packages. Routed through OpenConnector.

Group
External
Required app
openconnector
Storage
External (no local store)
Icon
Briefcase

How a leaf is wired

Each leaf has three pieces. The provider is server-side; the registration is in @conduction/nextcloud-vue; the activation is in the consuming app's main.js. Every consuming app picks up every leaf automatically — there is no per-app glue.

1. PHP provider (openregister/lib/Service/Integration/Providers/)

class CalendarProvider extends AbstractIntegrationProvider
{
public function getId(): string { return 'calendar'; }
public function getLabel(): string { return $this->l10n->t('Meetings'); }
public function getIcon(): string { return 'Calendar'; }
public function getGroup(): ?string { return 'comms'; }
public function getRequiredApp(): ?string { return 'calendar'; }
public function getStorageStrategy(): string { return 'link-table'; }
public function isEnabled(): bool { return $this->appManager->isInstalled('calendar'); }

public function list(string $register, string $schema, string $objectId, array $filters=[]): array
{
return $this->calendarEventService->getEventsForObject(objectUuid: $objectId);
}

// get / create / update / delete as the storage strategy supports.
}

The provider is registered with the DI container in Application::register() and pushed onto the IntegrationRegistry in Application::boot(). Storage strategy is 'magic-column' | 'link-table' | 'external' | 'query-time' (AD-22).

2. Vue registration (@conduction/nextcloud-vue/src/integrations/builtin/leaves.js)

import CnIntegrationTab from '../../components/CnIntegrationTab/CnIntegrationTab.vue'
import CnIntegrationCard from '../../components/CnIntegrationCard/CnIntegrationCard.vue'

window.OCA.OpenRegister.integrations.register({
id: 'calendar',
label: t('myapp', 'Meetings'),
icon: 'Calendar',
group: 'comms',
requiredApp: 'calendar',
order: 20,
referenceType: 'calendar',
tab: CnIntegrationTab,
widget: CnIntegrationCard,
defaultSize: { w: 4, h: 3 },
})

The generic CnIntegrationTab + CnIntegrationCard drive every leaf until any individual leaf needs a bespoke component, at which point the registration's tab / widget is repointed at a dedicated Vue file (the xWiki leaf has its own CnXwikiTab / CnXwikiCard already).

3. App-side activation ({consuming-app}/src/main.js)

import {
installIntegrationRegistry,
registerBuiltinIntegrations,
registerLeafIntegrations,
} from '@conduction/nextcloud-vue'

installIntegrationRegistry()
registerBuiltinIntegrations() // 5 always-on (files, notes, tags, tasks, audit-trail)
registerLeafIntegrations() // 18 NC-app and external leaves

That's the full wiring. Three calls. Every leaf the user has the required NC app for shows up automatically.

Storage strategies

Leaves declare one of four storage strategies. The registry uses this to choose the dispatch path; the docs page for each leaf explains the specifics.

StrategyWhere the link livesExample leaves
magic-columnA column on the object's table row. Cheapest.Files
link-tableA dedicated join table (openregister_{leaf}_links).Notes, Tags, Tasks, Calendar, Contacts, Deck, Email
externalNowhere local. Routed through OpenConnector on every CRUD.xWiki, OpenProject
query-timeNowhere local. Computed fresh on every list() call.Audit trail, Activity, Shares

'query-time' providers throw NotImplementedException on create() / update() / delete() per AD-22 — there is no local store to write to.

Required-app gating

Every NC-app-backed leaf declares its requiredApp. The registry filters in three stages (AD-5):

  1. PHP isEnabled() — returns false when the required NC app isn't installed. The OCS capabilities response marks the leaf enabled: false.
  2. JS-side filterCnObjectSidebar :use-registry honours the same gate. Disabled leaves don't render a tab.
  3. Admin UI — the integrations page still lists disabled leaves so admins know what's available, with a "needs <app> installed" message and an install hint.

Built-in leaves (files, notes, tags, tasks, audit-trail) and the shares core leaf return requiredApp: null — they ride on Open Register itself and are always available.

Collision policy (AD-13)

Re-registering an existing leaf id is a no-op in production (the first registration wins) and throws in development. So a consuming app can pre-register a leaf id with a bespoke tab / widget to override the generic pair without touching the library:

import CnMyAppCalendarTab from './components/CnMyAppCalendarTab.vue'
import CnMyAppCalendarCard from './components/CnMyAppCalendarCard.vue'

// Run this BEFORE registerLeafIntegrations() so the override wins.
window.OCA.OpenRegister.integrations.register({
id: 'calendar', // same id as the generic registration
label: t('myapp', 'Meetings'),
icon: 'Calendar',
group: 'comms',
requiredApp: 'calendar',
tab: CnMyAppCalendarTab, // bespoke
widget: CnMyAppCalendarCard, // bespoke
})

registerLeafIntegrations() // no-op on 'calendar' — first wins

This is how xWiki ships its richer CnXwikiTab (breadcrumbs, text-preview) on top of the same id: 'xwiki' slot.

Surfaces (AD-19)

Every leaf renders in up to four surfaces from the same registration. The widget component receives a surface prop and branches on it.

SurfaceWhere it appearsDefault behaviour
detail-pageObject detail pageFull linked-list with row actions
user-dashboardPersonal Open Register dashboardCompact list, max 5 entries
app-dashboardPer-app dashboard widgetCompact list, scoped to the app
single-entityA schema property of type reference with referenceType: '<id>'A chip resolved by id

The registration descriptor can pass surface-specific overrides (widgetCompact, widgetExpanded, widgetEntity); absent overrides fall back to the main widget.

  • xWiki leaf — the worked external example (OpenConnector-backed).
  • Calendar leaf — the worked NC-native example (CalDAV link-table).
  • Pluggable integration registry reference — the full ADR-019 contract.
  • Verification report — live status for all 24 advertised providers from the smoke harness.
  • Build your own leafscripts/scaffold-integration.sh <id> in the openregister repo generates the openspec change, PHP provider stub, and JS registration stub.
Add your own leaf

Pre-register an id before registerLeafIntegrations() and you can ship a bespoke Vue tab / widget for any leaf. The library never clobbers an existing registration.