Skip to main content
AcademytutorialBuild a Nextcloud app on the Conduction stack — Part 1: Scaffold

Build a Nextcloud app on the Conduction stack — Part 1: Scaffold

Clone the Conduction app template, rename it to DeskDesk, build, enable, and see the canonical app chassis. The first of six parts that build a working desk-booking app on the full Conduction stack.

TutorialApp developmentNextcloudOpenRegisternextcloud-vueTutorial series
9 min read

This is Part 1 of the DeskDesk tutorial series — the hands-on chassis lap. If you haven't read Part 0: Three paths, one curriculum yet, that's the orientation that explains why we build on this stack rather than raw Nextcloud, and how the manifest and @conduction/nextcloud-vue work as two intertwined surfaces. Part 1 picks up after that decision is made.

You'll build DeskDesk, a flexible desk-booking app for an open-office environment, on the full Conduction Nextcloud stack. The end product: pick a desk on a floor, book a slot, see your booking in your Nextcloud Calendar, and surface zone-specific knowledge articles from xWiki right next to the booking. Every piece reuses what @conduction/nextcloud-vue and OpenRegister already give you for free.

In Part 1 you scaffold the app, rename it, build it, and see the canonical app chassis on screen. No data, no integrations yet. We get the bones right first.

What we're building, across the series

PartTitleAdds
0Three paths, one curriculumThe orientation lap — which path to pick and why
1Scaffold (you're here)Empty app shell, chassis visible, navigable
2Schemas + manifestDesk and booking schemas, full CRUD, dashboard
3Schema-driven integrationsBookings appear in NC Calendar via the OpenRegister calendar provider
4External knowledge + shipxWiki source via OpenConnector, sidebar tab, package, publish
5Advanced manifest featuresactionToggles, fieldWidgets, public-mode pages, the other page types
6IntegrateCross-register reads, OpenConnector source/sync, two-way webhook back into OR

Same shape, two repos:

Step 1: Use the template

ConductionNL/nextcloud-app-template is a GitHub repository template. The "Use this template" button gives you a fresh repo with the full starter kit: Vue 2 + Pinia frontend, PHP backend, OpenRegister wiring, quality pipeline, OpenSpec scaffolding, GitHub Actions.

gh repo create ConductionNL/deskdesk \
  --template ConductionNL/nextcloud-app-template \
  --public \
  --description "Flexible desk booking for open-office environments"

Then clone it next to your other Nextcloud apps. Most workspaces keep them in a single apps-extra/ directory the dev container mounts.

cd /path/to/your/nextcloud/workspace/apps-extra
git clone https://github.com/ConductionNL/deskdesk.git
cd deskdesk

You now have a directory with the full template content under the new name. Nothing inside has been renamed yet — the directory is deskdesk/, but every file still says app-template, AppTemplate, OCA\AppTemplate. Step 2 fixes that.

Step 2: Rename the app

Nextcloud requires three identifiers to line up:

  • The directory name (deskdesk/) ✅ already
  • The <id> in appinfo/info.xml
  • The PHP namespace (OCA\AppTemplateOCA\DeskDesk)

Plus a handful of supporting files reference the old id. The full list is small enough to do by hand, and doing it by hand is the right move. Project memory: never use sed or scripted edits on code files — use a real editor with project-aware refactoring, or just read each file once before you change it.

2a. The boot-critical files

These are the ones that prevent Nextcloud from booting the app at all. Edit each with Find & replace all in your editor — AppTemplateDeskDesk and app-templatedeskdesk:

FileReplace
appinfo/info.xml<id>, <name>, <namespace>, <navigation>, <settings> paths
composer.jsonname, description, the psr-4 autoload prefix
package.jsonname
webpack.config.jsthe appId constant
templates/index.php and templates/settings/admin.phpOCA\AppTemplateOCA\DeskDesk, the data-* element id
lib/AppInfo/Application.phpnamespace, APP_ID constant, docblock
Every other PHP file in lib/namespace OCA\AppTemplate\…namespace OCA\DeskDesk\…, every use OCA\AppTemplate\… import, every docblock
appinfo/routes.phpdocblock only
Every Vue file in src/the t('app-template', '…') translation namespace becomes t('deskdesk', '…')
src/router/index.jsgenerateUrl('/apps/app-template')generateUrl('/apps/deskdesk')
src/store/store.js, src/store/modules/settings.js'/apps/app-template/api/settings''/apps/deskdesk/api/settings', fallback register slug
src/settings.jsloadTranslations('app-template', …) and the #app-template-settings mount selector
lib/Settings/app_template_register.jsonrename to lib/Settings/deskdesk_register.json (and the app field inside it)

2b. The "you can do later" files

Tests (tests/Unit/AppTemplateTest.php, the integration Postman collection), phpcs.xml / phpmd.xml / REUSE.toml headers, and the .github/ workflow inputs. They reference the template id in metadata only; the app boots fine without them touched. Fix them when you set up CI.

2c. One Nextcloud-version compatibility nip

The template's lib/AppInfo/Application.php registers a repair step at runtime:

$context->registerRepairStep(InitializeSettings::class);

registerRepairStep() is missing from IRegistrationContext on a few Nextcloud builds (you'll see Call to undefined method in nextcloud.log). Move it to appinfo/info.xml instead — same effect, more portable:

<repair-steps>
    <install>
        <step>OCA\DeskDesk\Repair\InitializeSettings</step>
    </install>
    <post-migration>
        <step>OCA\DeskDesk\Repair\InitializeSettings</step>
    </post-migration>
</repair-steps>

Then drop the registerRepairStep() line and the use OCA\DeskDesk\Repair\InitializeSettings; import from lib/AppInfo/Application.php.

Step 3: Build and enable

The template ships its build output uncommitted. So the first thing to do in a fresh clone is install dependencies and build the JS bundles, otherwise the app UI is just a blank <div id="content">.

composer install --no-dev
composer dump-autoload
npm install --legacy-peer-deps
npm run build

Then make sure your Nextcloud container can see the directory. In a typical Docker setup the apps-extra/ host directory is mounted at /var/www/html/custom_apps/ in the container; if your compose file uses one bind mount per app, add a line for deskdesk and restart. Otherwise:

docker cp ./deskdesk nextcloud:/var/www/html/custom_apps/
docker exec -u root nextcloud chown -R www-data:www-data /var/www/html/custom_apps/deskdesk

Now enable:

docker exec nextcloud php occ app:enable deskdesk

You should see:

deskdesk 0.1.0 enabled

Open http://localhost:8080/apps/deskdesk/ and log in. You see a placeholder dashboard with sample KPI cards, two empty panels, three nav items (Dashboard / Items / Documentation) and a Settings entry pinned to the bottom of the rail. That's the chassis.

The chassis: the whole point of Part 1

Every Conduction app — DeskDesk, OpenRegister, OpenCatalogi, Procest, MyDash, the dozen others — looks the same way on first sight. Same five structural pieces, same place, same behaviour. That recognisability is what @conduction/nextcloud-vue enforces: a user who learnt one app navigates the next one without docs.

The chassis is one shape, five atoms — the same five structural pieces in the same place, app after app.

In Part 2, you'll learn how manifest.json plus a JSON schema fills these atoms with real data. The placeholder Items nav entry, the empty dashboard, and the "article" schema you saw on screen will all become desks, bookings, and a real occupancy dashboard — without you laying out a single atom by hand.

Troubleshooting

What's next

Next steps