Install
$ agentstack add skill-impertio-studio-nextcloud-claude-skill-package-nextcloud-impl-app-development ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
nextcloud-impl-app-development
Quick Reference
Full-Stack App Layer Map
| Layer | PHP (Backend) | Vue.js (Frontend) | |-------|--------------|-------------------| | Entry point | lib/AppInfo/Application.php | src/main.js | | Routing | appinfo/routes.php | @nextcloud/router | | Controllers | lib/Controller/*.php | N/A | | Services | lib/Service/*.php | src/services/*.js | | Data access | lib/Db/Entity.php + Mapper.php | @nextcloud/axios | | State bridge | IInitialState::provideInitialState() | loadState() | | UI components | N/A | @nextcloud/vue | | Notifications | N/A | @nextcloud/dialogs |
Development Commands
| Command | Purpose | |---------|---------| | npm run dev | Build frontend for development | | npm run build | Build frontend for production | | npm run watch | Rebuild on file changes | | npm run serve | Dev server with HMR | | php occ app:enable myapp | Enable the app | | php occ app:disable myapp | Disable the app | | php occ migrations:migrate myapp | Run database migrations |
Critical Warnings
ALWAYS set ` in appinfo/info.xml` -- auto-wiring and autoloading depend on it.
ALWAYS use @nextcloud/axios for HTTP requests -- it handles CSRF tokens and authentication automatically.
ALWAYS use provideInitialState() in PHP and loadState() in JS for server-to-client data -- NEVER inject data via inline scripts or global variables.
ALWAYS use CSS custom properties (--color-*) for colors -- NEVER hardcode colors (breaks dark mode and theming).
ALWAYS use direct component imports (@nextcloud/vue/components/NcButton) -- barrel imports increase bundle size.
NEVER use \OCP\Server::get() for service resolution -- use constructor injection for testability.
NEVER use OCP\ILogger -- deprecated since NC 24. Use Psr\Log\LoggerInterface.
NEVER use raw fetch() or plain axios -- use @nextcloud/axios which handles auth headers automatically.
NEVER call loadState() without a fallback for optional data -- it throws on missing keys.
NEVER modify existing migration files -- create new migrations for schema changes.
Decision Tree: Route Type Selection
Is this endpoint consumed by external clients or other apps?
├── YES: Will responses need the OCS JSON/XML envelope?
│ ├── YES → Use OCS route + OCSController
│ │ Route: 'ocs' => [['name' => 'api#method', 'url' => '/api/v1/...']]
│ └── NO → Use regular route + ApiController (adds CORS)
│ Route: 'routes' => [['name' => 'api#method', 'url' => '/api/...']]
└── NO: Internal app use only
├── Page rendering? → Use Controller + TemplateResponse
│ Route: 'routes' => [['name' => 'page#index', 'url' => '/']]
├── CRUD resource? → Use resource routes
│ Route: 'resources' => ['item' => ['url' => '/items']]
└── AJAX from Vue frontend? → Use regular route + JSONResponse
Route: 'routes' => [['name' => 'item#create', 'url' => '/items']]
Essential Patterns
Pattern 1: Full-Stack App Creation (Step-by-Step)
Step 1: App manifest (appinfo/info.xml)
taskboard
Task Board
Simple task management
A task board for managing project tasks
1.0.0
AGPL-3.0-or-later
Developer Name
TaskBoard
organization
Task Board
taskboard.page.index
app.svg
Step 2: Application bootstrap (lib/AppInfo/Application.php)
[
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
],
'ocs' => [
['name' => 'task_api#index', 'url' => '/api/v1/tasks', 'verb' => 'GET'],
['name' => 'task_api#show', 'url' => '/api/v1/tasks/{id}', 'verb' => 'GET'],
['name' => 'task_api#create', 'url' => '/api/v1/tasks', 'verb' => 'POST'],
['name' => 'task_api#update', 'url' => '/api/v1/tasks/{id}', 'verb' => 'PUT'],
['name' => 'task_api#destroy', 'url' => '/api/v1/tasks/{id}', 'verb' => 'DELETE'],
],
];
Step 4-8: See [references/examples.md](references/examples.md) for the complete Entity, Mapper, Service, Controller, and Vue.js implementation.
Pattern 2: Initial State Bridge (PHP to JavaScript)
PHP side -- provide data in controller:
use OCP\AppFramework\Services\IInitialState;
class PageController extends Controller {
public function __construct(
string $appName,
IRequest $request,
private IInitialState $initialState,
private TaskService $service,
private ?string $userId,
) {
parent::__construct($appName, $request);
}
#[NoAdminRequired]
#[NoCSRFRequired]
public function index(): TemplateResponse {
// Eager: always serialized
$this->initialState->provideInitialState(
'tasks',
$this->service->findAll($this->userId)
);
// Lazy: only serialized when loaded by frontend
$this->initialState->provideLazyInitialState(
'config',
fn () => $this->service->getConfig()
);
return new TemplateResponse('taskboard', 'main');
}
}
JavaScript side -- consume data:
import { loadState } from '@nextcloud/initial-state'
// ALWAYS provide fallback for optional data
const tasks = loadState('taskboard', 'tasks', [])
const config = loadState('taskboard', 'config', { maxTasks: 100 })
Pattern 3: Frontend API Service Layer
// src/services/TaskService.js
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
const baseUrl = generateOcsUrl('/apps/taskboard/api/v1')
export async function fetchTasks() {
const response = await axios.get(`${baseUrl}/tasks`)
return response.data.ocs.data
}
export async function createTask(title, description) {
const response = await axios.post(`${baseUrl}/tasks`, { title, description })
return response.data.ocs.data
}
export async function updateTask(id, data) {
const response = await axios.put(`${baseUrl}/tasks/${id}`, data)
return response.data.ocs.data
}
export async function deleteTask(id) {
await axios.delete(`${baseUrl}/tasks/${id}`)
}
Pattern 4: Vue.js App Entry Point
// src/main.js
import Vue from 'vue'
import App from './App.vue'
const appElement = document.getElementById('content')
new Vue({
el: appElement,
render: h => h(App),
})
Pattern 5: Vue Component with Nextcloud UI
{{ selectedTask.title }}
{{ selectedTask.description }}
import NcContent from '@nextcloud/vue/components/NcContent'
import NcAppContent from '@nextcloud/vue/components/NcAppContent'
import NcAppNavigation from '@nextcloud/vue/components/NcAppNavigation'
import NcAppNavigationItem from '@nextcloud/vue/components/NcAppNavigationItem'
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
import { loadState } from '@nextcloud/initial-state'
import { showSuccess, showError } from '@nextcloud/dialogs'
import '@nextcloud/dialogs/style.css'
import { fetchTasks, deleteTask } from './services/TaskService'
export default {
name: 'App',
components: {
NcContent,
NcAppContent,
NcAppNavigation,
NcAppNavigationItem,
NcEmptyContent,
},
data() {
return {
tasks: loadState('taskboard', 'tasks', []),
selectedTask: null,
}
},
methods: {
selectTask(task) {
this.selectedTask = task
},
},
}
.task-detail {
padding: 20px;
color: var(--color-main-text);
}
Pattern 6: Webpack Configuration
// webpack.config.js
const webpackConfig = require('@nextcloud/webpack-vue-config')
module.exports = webpackConfig
// package.json (relevant sections)
{
"scripts": {
"build": "webpack --node-env production --progress",
"dev": "webpack --node-env development --progress",
"watch": "webpack --node-env development --progress --watch",
"serve": "webpack --node-env development serve --progress"
},
"dependencies": {
"@nextcloud/axios": "^2.0.0",
"@nextcloud/dialogs": "^5.0.0",
"@nextcloud/initial-state": "^2.0.0",
"@nextcloud/router": "^3.0.0",
"@nextcloud/vue": "^8.0.0",
"vue": "^2.7.0"
},
"devDependencies": {
"@nextcloud/webpack-vue-config": "^6.0.0"
}
}
Development Lifecycle
New App Checklist
- Scaffold -- Use https://apps.nextcloud.com/developer/apps/generate or create manually
- Configure -- Set
info.xmlwith correct `,,` - Backend -- Create Entity, Mapper, Service, Controller chain
- Database -- Create migration in
lib/Migration/ - Routes -- Define in
appinfo/routes.php - Frontend -- Set up
src/main.js,App.vue, webpack config - Bridge -- Connect PHP and JS via
IInitialState/loadState() - Build -- Run
npm install && npm run build - Enable -- Run
php occ app:enable myapp - Test -- Verify in browser, check browser console and Nextcloud log
File Naming Conventions
| Layer | Convention | Example | |-------|-----------|---------| | Entity | Singular PascalCase | lib/Db/Task.php | | Mapper | Entity + Mapper | lib/Db/TaskMapper.php | | Service | Entity + Service | lib/Service/TaskService.php | | Controller | Entity + Controller or ApiController | lib/Controller/TaskApiController.php | | Migration | Version{MMDD}Date{timestamp} | lib/Migration/Version1000Date20240101000000.php | | Vue component | PascalCase .vue | src/components/TaskItem.vue | | JS service | PascalCase .js | src/services/TaskService.js |
Reference Links
- [references/methods.md](references/methods.md) -- Full-stack development patterns and API reference
- [references/examples.md](references/examples.md) -- Complete app example: Entity, Mapper, Service, Controller, Vue.js
- [references/anti-patterns.md](references/anti-patterns.md) -- Development workflow mistakes
Official Sources
- https://docs.nextcloud.com/server/latest/developermanual/appdevelopment/intro.html
- https://docs.nextcloud.com/server/latest/developermanual/appdevelopment/bootstrap.html
- https://docs.nextcloud.com/server/latest/developermanual/diggingdeeper/controllers.html
- https://docs.nextcloud.com/server/latest/developer_manual/basics/storage/database.html
- https://docs.nextcloud.com/server/latest/developer_manual/basics/front-end/js.html
- https://github.com/nextcloud-libraries/nextcloud-vue
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Impertio-Studio
- Source: Impertio-Studio/Nextcloud-Claude-Skill-Package
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.