Build a Nextcloud app on the Conduction stack — Part 3: Schema-driven integrations
One calendarProvider block on the order schema. Order ship-dates appear in NC Calendar with no controller, no event listener, no per-app glue.
This is Part 3 of the nine-part app-building tutorial series. Part 2 wired the pet and order schemas. Part 3 makes those orders appear in every user's Nextcloud Calendar (without writing a controller, an event listener, or any per-app calendar glue).
The trick: OpenRegister already ships an ICalendarProvider (RegisterCalendarProvider) that exposes any schema with a calendarProvider block as a virtual calendar. You declare the block on the order schema. NC Calendar shows the events. That's the whole story.
Why not OpenConnector or a custom listener?
When you first hear "sync PetStore order ship-dates to Nextcloud Calendar" your instinct is probably one of:
- Write an OpenConnector flow that listens to
OrderCreatedEventand POSTs to the CalDAV endpoint - Subclass
IEventListener<OrderCreatedEvent>in PHP and create a CalDAV event by hand - Have the frontend
POST /remote.php/dav/...when the user clicks Place Order
All three work. All three are wrong for this case, because they treat NC Calendar as a destination you push to. OpenRegister already exposes every schema as a calendar. You're not pushing anywhere; you're declaring that the order schema is a calendar source.
The same pattern applies to:
| Integration | Schema annotation | What you get |
|---|---|---|
| NC Calendar | calendarProvider block | Read-only virtual calendar per schema |
| NC Activity | (automatic, free) | CRUD events in the activity stream + dashboard widget + email digest |
| NC Mail Smart Picker | linkedTypes: ["email"] | Paste a pet URL into NC Mail and get a rich preview |
| NC Contacts | linkedTypes: ["contact"] | /api/contacts/match returns matching pet owners |
Anti-pattern: hand-coding <App>CalendarSync.vue, <App>NotesService, <App>MailSidebar.vue. Use the providers.
Step 1: Add the calendarProvider block
Open lib/Settings/petstore_register.json and add a configuration block to the order schema:
"order": {
"slug": "order",
"title": "Order",
/* ... existing properties ... */
"configuration": {
"calendarProvider": {
"enabled": true,
"dtstart": "shipDate",
"titleTemplate": "Order #{id} · {pet}",
"color": "#003a8c"
}
}
}
The four fields:
enabled:trueexposes the schema as a virtual calendar.dtstart: name of the property that holds the event start. We use theshipDateproperty already on every order.dtend: optional. When missing (as here), OpenRegister falls back to a sensible default (next-hour for date-time, end-of-day for date-only). Orders are point-in-time events, no end needed.titleTemplate: string template for the event summary. Tokens in{braces}are resolved against the object's properties. We useOrder #{id} · {pet}. To resolve relations (thepetproperty is a uuid pointing at apetobject), include_extend=relationswhen the calendar provider fetches. See the OpenRegister side.color: hex colour of the calendar. We use cobalt-blue (--c-blue-cobalt).
That's the whole change. No other file needs to be touched.
Step 2: Re-import the configuration
Schemas in OpenRegister have a version. Bumping the schema version + re-running the import is the canonical way to update them in place. Bump the file's info.version (0.2.0 → 0.3.0) and the order schema's version:
"info": { "version": "0.3.0" },
"order": { "version": "0.3.0", /* ... */ }
Then trigger the import the same way as Part 2:
docker exec nextcloud php occ app:disable petstore
docker exec nextcloud php occ app:enable petstore
(Or hit POST /api/settings/load if you've wired admin settings yet.)
Step 3: Verify in NC Calendar
Open /apps/calendar/. You should see a new entry in the calendar list:
- Order: cobalt-blue colour, read-only badge, owned by your user
Click it. The seed orders from Part 2 (the Monday ship-date for Order #1 'doggie', the Tuesday ship-date for Order #2 'tabby', the Wednesday ship-date for Order #3 'koi') appear as events. Click one. The popover shows the order id + pet title + the ship date.
If you don't see the calendar:
- Confirm OpenRegister registered its provider:
docker exec nextcloud php occ config:list | grep openregister - Make sure your
orderschema hasconfiguration.calendarProvider.enabled: trueafter the re-import - Check
nextcloud.logforRegisterCalendarProvidermessages
How it works under the hood
OpenRegister's RegisterCalendarProvider implements Nextcloud's ICalendarProvider interface. On every Calendar API request, it:
- Lists every schema with
configuration.calendarProvider.enabled === true - For each, returns a
RegisterCalendar(a lightweightICalendarimplementation) - When the user opens that calendar, queries OpenRegister for objects where
dtstartis in the requested range - Transforms each object into an iCalendar VEVENT via
CalendarEventTransformer, applying thetitleTemplate
Per-object ACLs gate event content. The provider bypasses multi-tenancy when listing schemas (so cross-tenant calendars work), but every individual event is still gated by RBAC.
The same pattern is followed by the spec.
What you've now bought yourself
Activity (NC Activity) is already wired. Browse /apps/activity/ and you'll see your order creates / updates. The dashboard activity widget + email digest both show PetStore events. Zero code on your end.
Mail Smart Picker too: in NC Mail, paste a pet URL (/apps/petstore/pets/pet-doggie) and the picker resolves it into a rich preview card with the pet name + category + status.
You've effectively integrated PetStore into four Nextcloud apps with one JSON block. That's the schema-driven discipline paying off.