Ga naar hoofdinhoud
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 PetStore, build, enable, and see the canonical app chassis. The first hands-on part of the nine-part app-building series, using the OpenAPI Pet Store as the running domain.

TutorialApp developmentNextcloudOpenRegisternextcloud-vueTutorial series
10 min read

This is Part 1 of the app-building 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 PetStore, a Nextcloud app on top of the OpenAPI Pet Store domain (pets, orders, categories, tags) on the full Conduction Nextcloud stack. The end product: add pets to the store, place orders, see order ship-dates in your Nextcloud Calendar, and surface category-specific care guides from xWiki right next to each pet. 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 + manifestPet, order, category schemas, full CRUD, dashboard
3Schema-driven integrationsOrder ship-dates appear in NC Calendar via the OpenRegister calendar provider
4External knowledge + shipxWiki care-guides 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
7The nc-vue component libraryCapability-driven tour: forms, dialogs, status, filtering, composables, theming
8Document and showcaseStand up a docs site for the app you built, with screen mocks + Playwright capture

Same shape, two repos:

At the time of writing, Conduction/petstore is the canonical reference repo for this tutorial. If it doesn't exist yet on Codeberg, fork from the template into your own org as shown in Step 1; the tutorial body uses petstore throughout as the example slug.

Step 1: Use the template

Conduction/nextcloud-app-template is a repository template. The "Use this template" button on Codeberg (or GitHub's mirror) gives you a fresh repo with the full starter kit: Vue 2 + Pinia frontend, PHP backend, OpenRegister wiring, quality pipeline, OpenSpec scaffolding, CI workflows.

gh repo create ConductionNL/petstore \
  --template ConductionNL/nextcloud-app-template \
  --public \
  --description "Pet store: pets, orders, categories on the Conduction stack"

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://codeberg.org/Conduction/petstore.git
cd petstore

You now have a directory with the full template content under the new name. Nothing inside has been renamed yet. The directory is petstore/, 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 (petstore/) ✅ already
  • The <id> in appinfo/info.xml
  • The PHP namespace (OCA\AppTemplateOCA\PetStore)

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 (AppTemplatePetStore and app-templatepetstore):

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\PetStore, the data-* element id
lib/AppInfo/Application.phpnamespace, APP_ID constant, docblock
Every other PHP file in lib/namespace OCA\AppTemplate\…namespace OCA\PetStore\…, every use OCA\AppTemplate\… import, every docblock
appinfo/routes.phpdocblock only
Every Vue file in src/the t('app-template', '…') translation namespace becomes t('petstore', '…')
src/router/index.jsgenerateUrl('/apps/app-template')generateUrl('/apps/petstore')
src/store/store.js, src/store/modules/settings.js'/apps/app-template/api/settings''/apps/petstore/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/petstore_register.json AND change the "app": "app-template" field inside it to "app": "petstore" (it's easy to miss after the file rename)
appinfo/info.xml <repair-steps> blockthe template already ships this block. Update the FQNs inside from OCA\AppTemplate\Repair\InitializeSettings and InitializeActions to the OCA\PetStore\Repair\… equivalents
appinfo/info.xml <settings> blockupdate the <admin>OCA\AppTemplate\Settings\AdminSettings</admin> FQN to the OCA\PetStore\… equivalent

2b. The "you can do later" files

Tests (tests/Unit/AppTemplateTest.php, the integration Postman collection), phpcs.xml / phpmd.xml / REUSE.toml headers, README.md, and the .github/ or .forgejo/ 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 also registers the repair step at runtime, in addition to the <repair-steps> block you updated in 2a:

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

registerRepairStep() is missing from IRegistrationContext on a few Nextcloud builds (you'll see Call to undefined method in nextcloud.log). Since appinfo/info.xml already has the equivalent <repair-steps> block, the runtime registration is redundant. Drop the registerRepairStep() line and the use OCA\PetStore\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 petstore and restart. Otherwise:

tar --exclude=node_modules --exclude=vendor -czf /tmp/petstore.tgz petstore/
docker cp /tmp/petstore.tgz nextcloud:/tmp/petstore.tgz
docker exec -u root nextcloud bash -c 'cd /var/www/html/custom_apps && tar xzf /tmp/petstore.tgz && chown -R www-data:www-data petstore && rm /tmp/petstore.tgz'
Why tar + cp instead of docker cp ./petstore … directly

A fresh node_modules/ can be 400+ MB. A direct docker cp on the whole directory takes minutes per iteration and silently hangs without progress output. The container doesn't need node_modules/ because Nextcloud only serves the built js/ bundle. Tar with the excludes keeps each rebuild cycle under 10 seconds.

Now enable:

docker exec nextcloud php occ app:enable petstore

You should see (the version comes from appinfo/info.xml, usually 0.1.x on a fresh template):

petstore 0.1.2 enabled

Open http://localhost:8080/apps/petstore/ 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 (PetStore, OpenRegister, OpenCatalogi, Procest, LaunchPad, 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.

Unknown app: launchpad

The diagram above shows the LaunchPad reference (one of the canonical fleet variants) with the chassis you just enabled rendered in design tokens. Your fresh PetStore install lays out the same way: topbar, left nav, main column with a page header, optional right sidebar.

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 pets, orders, and a real inventory dashboard, without you laying out a single atom by hand.

Troubleshooting

Troubleshooting

The js/ build output is not committed. Run npm run build before you enable the app, or after every frontend change. The blank-rectangle symptom always means "the bundle is missing".

Nextcloud requires the directory name to exactly match the <id> in appinfo/info.xml. If you cloned the repo under a different name, either rename the directory or add a relative symlink: ln -s your-clone-name ../petstore.

Step 2c covers this. The fix is to move the repair-step registration from lib/AppInfo/Application.php into appinfo/info.xml's <repair-steps> block.

Run composer dump-autoload from the app directory. PHP's classmap caches the old OCA\AppTemplate map until you regenerate it.

What's next

Volgende stappen