{"id":437970,"date":"2026-09-29T11:15:18","date_gmt":"2026-09-29T09:15:18","guid":{"rendered":"https:\/\/nextcloud.com\/?p=437970"},"modified":"2026-09-29T11:15:26","modified_gmt":"2026-09-29T09:15:26","slug":"nextcloud-app-development-beginners-guide","status":"publish","type":"post","link":"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/","title":{"rendered":"Nextcloud app development beginner&rsquo;s guide: What you should know before building your first app"},"content":{"rendered":"\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Building your first Nextcloud app (beginner workshop)\" width=\"500\" height=\"281\" src=\"https:\/\/www.youtube.com\/embed\/BQlm71K1AVM?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">To build a Nextcloud app that works with different databases, thousands of users, and unpredictable server configurations, it&rsquo;s essential to understand why Nextcloud is built the way it is and where developers most often get tripped up when their code leaves their local test instance.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this article, you&rsquo;ll learn how a request actually moves through Nextcloud&rsquo;s architecture, how apps are meant to talk to each other (and why they shouldn&rsquo;t talk to each other directly), and where the line between public and private APIs really sits. You&rsquo;ll understand how Nextcloud models and reacts to data like entities, migrations, and indices, and how the event system lets apps stay loosely coupled from one another. Finally, you&rsquo;ll learn more about the mistakes that only show up at scale and what it takes to get an app certified and published to the Nextcloud App Store.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The most important thing to keep in mind: The most durable Nextcloud apps are the ones built with the platform&rsquo;s structure in mind, not around it.<\/p>\n\n\n\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_88 ez-toc-light-blue ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table des mati\u00e8res<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #050404;color:#050404\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #050404;color:#050404\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 eztoc-toggle-hide-by-default' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#nextcloud-under-the-hood\" >Nextcloud under the hood<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#how-nextcloud-code-actually-runs\" >How Nextcloud code actually runs<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#the-request-lifecycle\" >The request lifecycle<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#controller-attributes\" >Controller attributes<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#dependency-injection\" >Dependency injection<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#data-and-events-in-nextcloud\" >Data and events in Nextcloud<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#entities-and-type-casting\" >Entities and type casting<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#migrations\" >Migrations<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#indices\" >Indices<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#the-event-system\" >The event system<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#background-jobs\" >Background jobs<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#before-you-publish-your-nextcloud-app\" >Before you publish your Nextcloud app<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#what-trips-nextcloud-developers-up-at-scale\" >What trips Nextcloud developers up at scale<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#getting-into-the-nextcloud-app-store\" >Getting into the Nextcloud App Store<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#resources\" >Resources<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#conclusion\" >Conclusion<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#faq\" >FAQ<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"nextcloud-under-the-hood\"><\/span>Nextcloud under the hood<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">There is some institutional knowledge that experienced Nextcloud developers take for granted and that can easily trip up newcomers. Here&rsquo;s what you need to keep in mind before you start developing your first Nextcloud app.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"how-nextcloud-code-actually-runs\"><\/span>How Nextcloud code actually runs<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Before writing any code, it helps to know where the boundaries actually are, specifically what&rsquo;s public and stable, what&rsquo;s internal and liable to change without warning, and how core and shipped apps relate to one another.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Public vs. private APIs<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Nextcloud draws a hard line between its public and private APIs. Core functionality sits behind interfaces, and developers are expected to code against those interfaces rather than concrete implementations. Importantly, the apps Nextcloud ships itself (e.g. Talk or Calendar) use the exact same public APIs that third-party developers have access to. There&rsquo;s no special internal-only shortcut. If it works for Nextcloud&rsquo;s own teams, it&rsquo;s built on the same contract available to everyone else.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A key design principle is that apps don&rsquo;t call each other&rsquo;s code directly. Instead, they listen to events and react independently. This keeps apps from depending on one another being installed, which matters a lot in an ecosystem where users pick and choose which apps to enable.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Core vs. shipped apps<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">At the core level, Nextcloud provides the platform itself: authentication, file storage, sharing, database abstractions, and routing. On top of that, a set of apps ship alongside the server but live in their own repositories, such as DAV (the abstraction layer for WebDAV, the protocol Nextcloud uses to read and write files safely, even when multiple people touch the same file at once), CalDAV and CardDAV, which extend that same idea to calendars and contacts. Administrative features like the settings panel are also built as their own apps, giving every app (including third-party ones) a consistent way to plug into the settings UI or the left sidebar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The contract between core and apps is called OCP (the public, stable API surface). Breaking changes to OCP are announced at least five versions ahead of time, so developers have plenty of warning before something they rely on changes. The <a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/\" target=\"_blank\" rel=\"noreferrer noopener\">Nextcloud developer manual<\/a> is the canonical reference for what counts as public.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full imageRoundShadow\"><img decoding=\"async\" width=\"1920\" height=\"1056\" src=\"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-7.png\" alt=\"OCP vs OC\" class=\"wp-image-438642\" srcset=\"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-7.png 1920w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-7-300x165.png 300w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-7-1024x563.png 1024w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-7-768x422.png 768w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-7-1536x845.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">OCP vs. OC<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Within that structure, it&rsquo;s worth understanding the difference between OCP and OC.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">OCP is the public, documented, stable interface, and therefore always the safe choice.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">OC is internal, often legacy, code. Some very old hooks still only exist in OC, but using it should be the rare exception, not the rule.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To give a concrete example: <code>OC\\Files\\Storage\\Local<\/code> used to be a valid way to create local storage, but Nextcloud abstracted the storage layer to support things like S3 buckets and external drives, introducing <code>IStorage<\/code> in its place. The old OC class was eventually removed entirely, and any app still using it broke. As a rule of thumb, if something isn&rsquo;t documented in the developer manual, assume it can change without warning, and avoid using it if there&rsquo;s any alternative.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"the-request-lifecycle\"><\/span>The request lifecycle<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Every request into Nextcloud follows the same path from the moment it hits the server to the moment a response goes back out. Knowing that path, along with where security checks slot into it, makes it much easier to see where your own code belongs.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">From request to response<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Understanding how a request actually moves through Nextcloud helps explain where code belongs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An HTTP request first hits <code>index.php<\/code>, which routes it through <code>routes.php<\/code> to determine which controller method should handle it. From there, the controller calls a service, which in turn talks to a storage or database layer, before the response travels back up the same chain.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This layered structure (<a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/basics\/controllers.html\" target=\"_blank\" rel=\"noreferrer noopener\">controller<\/a>, service, <a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/basics\/storage\/database.html\" target=\"_blank\" rel=\"noreferrer noopener\">mapper, entity<\/a>) will be familiar to anyone who&rsquo;s worked with an MVC-style framework. Controllers translate the HTTP call into method calls, services do the actual work of processing and collecting data, and the split keeps the codebase testable and organized by responsibility.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Where security checks happen<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Security checks (authentication, CSRF token verification, and rate limiting) all happen <em>before<\/em> a controller method ever runs. Brute-force protection is the one exception: it happens <em>within<\/em> the controller itself, which matters if you want to throttle anonymous requests to a specific endpoint, such as a public share link.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"controller-attributes\"><\/span>Controller attributes<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Modern Nextcloud apps use PHP 8 attributes (the <code>#[...]<\/code> syntax) to declare how a controller method should be treated. For example, marking a route as a public page, exempting it from CSRF checks, or configuring brute-force throttling with a named action. Older apps may still use the previous approach (PHPDoc comments like <code>@PublicPage<\/code>), which still works but is considered legacy. New apps should use attributes.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full imageRoundShadow\"><img decoding=\"async\" width=\"1920\" height=\"1056\" src=\"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-9.png\" alt=\"Controller attributes\" class=\"wp-image-438666\" srcset=\"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-9.png 1920w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-9-300x165.png 300w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-9-1024x563.png 1024w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-9-768x422.png 768w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-9-1536x845.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Some attributes commonly appear together. For example, <code>PublicPage<\/code> and <code>NoCSRFRequired<\/code> often pair up for routes like a public profile page, where there&rsquo;s no form being submitted and therefore nothing to protect with a CSRF token. The full reference of available attributes lives in the <a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/basics\/controllers.html\" target=\"_blank\" rel=\"noreferrer noopener\">controllers section of the developer manual<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"dependency-injection\"><\/span>Dependency injection<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Nextcloud relies heavily on dependency injection to keep code flexible and testable. Understanding how it works also explains why controllers receive a bare user ID instead of a full user object.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full imageRoundShadow\"><img decoding=\"async\" width=\"1920\" height=\"1056\" src=\"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-10.png\" alt=\"Dependency injection\" class=\"wp-image-438660\" srcset=\"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-10.png 1920w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-10-300x165.png 300w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-10-1024x563.png 1024w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-10-768x422.png 768w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-10-1536x845.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">Interfaces instead of concrete classes<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Nextcloud wires up functionality through <a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/basics\/dependency_injection.html\" target=\"_blank\" rel=\"noreferrer noopener\">dependency injection<\/a>. Instead of a class instantiating its own dependencies, a container resolves them based on the interface being requested. So rather than injecting a concrete <code>LocalStorage<\/code> class, an app injects <code>IStorage<\/code>, and the container resolves it to whatever storage backend the admin has actually configured, be it a local disk, S3, or anything else. This also means developers can write their own storage backend (however unconventional) by implementing the <code>IStorage<\/code> interface, and any code that works against that interface will work with it automatically.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dependency injection also makes testing dramatically easier, since dependencies like <code>IAppConfig<\/code> or <code>IUserConfig<\/code> can be swapped for fakes or mocks in unit tests, without needing a real database.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">The nullable user ID<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">One Nextcloud-specific quirk worth knowing: Rather than injecting a full user object, controllers often receive a nullable <code>string $userId<\/code>, resolved automatically from the login session. It&rsquo;s nullable because an anonymous visitor to a public page won&rsquo;t have one. Even on pages that require authentication, it&rsquo;s still good defensive practice to check that the user ID is actually set.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The reason Nextcloud injects just the ID rather than the full user object is efficiency. Resolving a full user object requires a database read, while the ID alone (which is unique and permanent, and once set, can never be changed) is often enough to check access to a data resource without hitting the database at all.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"data-and-events-in-nextcloud\"><\/span>Data and events in Nextcloud<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nextcloud models data through entities backed by database migrations, keeps that data fast to query with well-placed indices, and reacts to changes across the system through events and background jobs. Getting this wrong can result in a slow query that only appears at scale, a migration that locks up an upgrade for hours, or an app that breaks the moment a dependency changes.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"entities-and-type-casting\"><\/span>Entities and type casting<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Entities are how Nextcloud represents a database row in code, and getting their type handling right is what keeps an app behaving consistently across every database it might end up running on.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Auto-generated getters and setters<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">An entity in Nextcloud is a representation of a database row. Rather than writing boilerplate getters and setters by hand, Nextcloud generates them automatically based on PHPDoc comments on the entity&rsquo;s properties (for example, a <code>userId<\/code> property produces <code>getUserId()<\/code> and <code>setUserId()<\/code> for free).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Developers can still override a generated method if they need custom processing, such as converting a raw date field before returning it. But the recommended approach is to only do this when genuine processing is happening, since overriding a getter to reshape a database value can introduce subtle bugs.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Type casting across databases<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Type casting is the other key concept here, covered in the manual&rsquo;s <a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/basics\/storage\/database.html\" target=\"_blank\" rel=\"noreferrer noopener\">database access section<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nextcloud supports four different databases (MariaDB, MySQL, Oracle, and PostgreSQL) and they don&rsquo;t agree on how they represent values like booleans or timestamps. Some databases store booleans as a tiny integer (0 or 1) and hand that back as a raw integer rather than a boolean. Without explicit type casting on the entity, a boolean field can come back as a string like <code>\"0\"<\/code>, which behaves unexpectedly in conditional logic. Casting a field to <code>datetime<\/code> similarly guarantees you get a proper date object back instead of a raw string, regardless of which database is running underneath.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"migrations\"><\/span>Migrations<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Changing a database schema safely, across every database Nextcloud supports and every possible upgrade path a user might be on, means following a specific process. This process is easy to get right in principle and expensive to get wrong in practice.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">The three-step process<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Database schema changes in Nextcloud follow a <a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/basics\/storage\/migrations.html\" target=\"_blank\" rel=\"noreferrer noopener\">three-step migration process<\/a>, conceptually similar to migrations in frameworks like Laravel&rsquo;s Eloquent:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A pre-schema change runs before the migration is applied, and is the place for anything that needs to happen to existing data before a table changes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A schema change is where tables get created or altered. This is the actual DDL step.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A post-schema change runs after the schema is in place, and is typically used for lighter data migrations, like populating a new column from existing data.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">The performance caveat<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">There&rsquo;s an important caveat: Migrations run during install or upgrade, so any post-schema-change step that touches a large amount of data can seriously slow down that process. In a real use case, a migration for Nextcloud&rsquo;s system address book once took six to seven hours on a large instance, because the team&rsquo;s own testing hadn&rsquo;t been done against an instance with that much data. The lesson from this is that if a migration involves heavy data processing, you should queue it as a one-time background job instead of running it inline during the migration itself.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"indices\"><\/span>Indices<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Choosing the right indices matters, and it&rsquo;s easy to get wrong. Sometimes a team only realizes a table needed an index after the table already has a lot of data in it, at which point adding one can be expensive.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For brand-new tables or ones you can be confident will stay small, an index can be <a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/basics\/storage\/index.html\" target=\"_blank\" rel=\"noreferrer noopener\">added directly in the schema-change step of a migration<\/a>. But for existing tables that might already hold a significant amount of data, Nextcloud provides a safer mechanism: registering the index as a \u00ab\u00a0missing index\u00a0\u00bb via a listener. This surfaces a warning in the admin settings panel and can be applied through Nextcloud&rsquo;s CLI tool (<code>occ<\/code>), letting an admin schedule the index creation during a planned maintenance window rather than forcing it to happen automatically.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Column data type matters too, since very long <code>varchar<\/code> columns are more expensive to index, and in MySQL specifically, columns longer than 4,096 characters can make an index counterproductive rather than helpful. For diagnosing slow queries in general, the database&rsquo;s <code>EXPLAIN<\/code> command is a useful tool for seeing whether a query is actually using an available index or scanning a whole table.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"the-event-system\"><\/span>The event system<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Events are the mechanism behind the principle of not calling other apps&rsquo; code directly. They let an app react to what&rsquo;s happening elsewhere in the system without ever depending on another app being installed.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full imageRoundShadow\"><img decoding=\"async\" width=\"1920\" height=\"1056\" src=\"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-16.png\" alt=\"The event system\" class=\"wp-image-438654\" srcset=\"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-16.png 1920w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-16-300x165.png 300w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-16-1024x563.png 1024w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-16-768x422.png 768w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-16-1536x845.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">How listeners work<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Events are the backbone of app-to-app integration in Nextcloud and are documented in the manual&rsquo;s <a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/basics\/events.html\" target=\"_blank\" rel=\"noreferrer noopener\">events section<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Core fires events for common actions like files being created, modified, or deleted, or more generally, \u00ab\u00a0nodes\u00a0\u00bb (DAV&rsquo;s term for either a file or a folder) being updated. An app that wants to react to file changes implements <code>IEventListener<\/code> and its <code>handle<\/code> method, checking early on whether the incoming event is actually the one it cares about (since many different events pass through the same listener mechanism), and can also inspect the data the event provides.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Custom events and loose coupling<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Apps aren&rsquo;t limited to reacting to core events. They can also define and fire their own custom events, which other apps can then listen to.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is what allows an app like Files Reminders to have no hard dependency on the Files app itself. It simply listens for file-related events and reacts to them, without knowing anything about how the Files app actually works internally. That means the app keeps working even if the Files app is refactored, or even if it isn&rsquo;t installed at all. In the latter case, the event won&rsquo;t fire, but nothing will break.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"background-jobs\"><\/span>Background jobs<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Not all work belongs in the request\/response cycle. Background jobs let an app offload slow or non-urgent tasks, but using them well means understanding how (and how reliably) they actually get triggered.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Triggering mechanisms<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/basics\/backgroundjobs.html\" target=\"_blank\" rel=\"noreferrer noopener\">Background jobs<\/a> exist to move slow, expensive work (anything that loops over a large file tree, processes a lot of data, or syncs from an external source, like Nextcloud&rsquo;s calendar subscription sync) out of the normal request\/response cycle. A background job extends <code>TimeJob<\/code>, receives an injected <code>ITimeFactory<\/code> (a testable wrapper around PHP&rsquo;s native time functions), and defines an interval, down to as little as one minute.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Whether that interval is actually respected in practice depends on how the instance is configured. Nextcloud supports <a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/admin_manual\/configuration_server\/background_jobs_configuration.html\" target=\"_blank\" rel=\"noreferrer noopener\">three ways of triggering scheduled jobs<\/a>: system cron, webcron, and AJAX.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">AJAX-based triggering (often used by smaller community instances that don&rsquo;t want to set up a system cron job) only runs queued jobs when an AJAX call happens to come in, with no guarantee of timing. Even with proper cron configured, larger instances sometimes run it every 15 minutes rather than the common 5-minute default, so background jobs shouldn&rsquo;t be written with an assumption of precise timing.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Maintenance windows<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Nextcloud also supports a <a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/admin_manual\/configuration_server\/background_jobs_configuration.html\" target=\"_blank\" rel=\"noreferrer noopener\">maintenance window<\/a>, which an admin can configure to indicate when the server has lower load. Background jobs can be marked as \u00ab\u00a0time insensitive,\u00a0\u00bb meaning they&rsquo;ll preferentially run during that window. This is appropriate for something like a calendar sync where updating overnight is perfectly fine. Jobs marked as time-sensitive ignore the maintenance window and simply run on their configured interval instead.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"before-you-publish-your-nextcloud-app\"><\/span>Before you publish your Nextcloud app<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Knowing how the Nextcloud platform works is only half the job. The app also needs to survive contact with real-world usage and clear the concrete steps needed to get certified and published to the Nextcloud App Store.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"what-trips-nextcloud-developers-up-at-scale\"><\/span>What trips Nextcloud developers up at scale<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Code that works fine in testing can fail badly once it hits real-world data volumes, because most developers test with small datasets.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Large file trees<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">A few hundred files or folders load fine. A hundred thousand will take real time. The fix is to use folder search with a limit and offset rather than pulling an entire tree, and to push full traversals into a background job.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Looping over many users<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">This is fine for a handful of users, but potentially very slow for thousands.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Oracle&rsquo;s query limits<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Oracle doesn&rsquo;t allow more than 1,000 items in an <code>IN<\/code> clause. Apps that are only tested against MariaDB or SQLite won&rsquo;t hit this and won&rsquo;t error until someone deploys the same app against an Oracle-backed instance, where it then fails. Nextcloud&rsquo;s own code handles this by chunking <code>IN<\/code> queries into batches of 1,000.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Version support<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">It&rsquo;s tempting to just bump the maximum supported version and move on, but it&rsquo;s worth actually testing against the current and previous major versions of Nextcloud.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">N+1 queries<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Rather than querying for each item individually (fetching 44 participants in a Talk room one at a time, for example), batch the lookup into a single <code>IN<\/code> query. This keeps the number of database round-trips from scaling linearly with the number of users, files, or other items involved.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"getting-into-the-nextcloud-app-store\"><\/span>Getting into the Nextcloud App Store<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Once your Nextcloud app is ready, getting it in front of users means passing through a certification process. Knowing the most common ways submissions get rejected can save a lot of back and forth.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full imageRoundShadow\"><img decoding=\"async\" width=\"1920\" height=\"1056\" src=\"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-21.png\" alt=\"Getting into the app store\" class=\"wp-image-438648\" srcset=\"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-21.png 1920w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-21-300x165.png 300w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-21-1024x563.png 1024w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-21-768x422.png 768w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-development-webinar-slide-21-1536x845.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">The certification process<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Publishing an app involves a few concrete checks. The app&rsquo;s <a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/app_development\/info.html\" target=\"_blank\" rel=\"noreferrer noopener\"><u><code>info.xml<\/code><\/u> file<\/a> is validated, the app itself must be <a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/app_publishing_maintenance\/code_signing.html\" target=\"_blank\" rel=\"noreferrer noopener\">cryptographically signed<\/a>, and a release tarball is uploaded. To get a signing certificate, developers submit a request through the <a href=\"https:\/\/github.com\/nextcloud\/app-certificate-requests\" target=\"_blank\" rel=\"noreferrer noopener\"><u><code>nextcloud\/app-certificate-requests<\/code><\/u><\/a> repository, which typically takes two to four days to process.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Common submission failures<\/h4>\n\n\n\n<ul class=\"wp-block-list nc-list\">\n<li>A certificate whose common name (CN) doesn&rsquo;t match the app ID declared in <code>info.xml<\/code>.<\/li>\n\n\n\n<li>An invalid archive structure. The tarball must contain exactly one top-level folder, named after the app ID (which itself can&rsquo;t exceed 512 characters).<\/li>\n\n\n\n<li>Signature verification failures caused by editing files <em>after<\/em> signing the package. All changes need to be finished before the signing step.<\/li>\n\n\n\n<li>An invalid category. Apps must fit into one of a fixed, limited set of categories.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Full details on the certification and release process are documented in the <a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/app_publishing_maintenance\/\" target=\"_blank\" rel=\"noreferrer noopener\">app store publishing guidelines<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"resources\"><\/span>Resources<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Beyond the <a href=\"https:\/\/docs.nextcloud.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">official documentation<\/a> (which covers admin, user, and developer guides separately), Nextcloud provides <a href=\"https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/digging_deeper\/continuous_integration.html\" target=\"_blank\" rel=\"noreferrer noopener\">reusable GitHub Actions workflow templates<\/a> for setting up CI, which should comfortably fit within GitHub&rsquo;s free tier for smaller projects.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For learning by example, it&rsquo;s recommended to read real, shipped Nextcloud apps. Files Reminders is small and demonstrates good patterns cleanly. Talk and Calendar are larger and show how those patterns hold up at scale. Talk in particular is noteworthy for its event handling and its signaling backend, which has to coordinate real-time behavior across desktop, web, Android, and iOS clients simultaneously.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Dependency injection, event listeners, and database migrations are all well-established patterns. In isolation, none of this is complicated.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">What trips developers up is assuming a shortcut is harmless. But skipping the public API for something more direct, having one app quietly depend on another&rsquo;s internals, or running a heavy migration inline instead of queuing it as a background job can cause problems at scale.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Building a Nextcloud app that holds up in real use cases comes down to one simple idea: Build with the platform&rsquo;s structure, not around it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Looking for hands-on, project-based courses for developers building on Nextcloud? Check out <a href=\"https:\/\/nextcloud.com\/academy\" target=\"_blank\" rel=\"noreferrer noopener\"><u>Nextcloud Academy<\/u><\/a>. It features several tracks teaching you how to build Nextcloud apps using PHP or Python, whatever you\u2019re more comfortable with.<\/p>\n\n\n<div class=\"wp-block-image imageRoundShadow\">\n<figure class=\"aligncenter size-large\"><a href=\"\/academy\" target=\"_blank\" rel=\" noopener\"><img decoding=\"async\" width=\"825\" height=\"576\" src=\"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/NC-academy-website-web-825x576.jpg\" alt=\"\" class=\"wp-image-434053\" srcset=\"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/NC-academy-website-web-825x576.jpg 825w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/NC-academy-website-web-300x209.jpg 300w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/NC-academy-website-web-768x536.jpg 768w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/NC-academy-website-web-1536x1072.jpg 1536w, https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/NC-academy-website-web.jpg 1920w\" sizes=\"(max-width: 825px) 100vw, 825px\" \/><\/a><\/figure>\n<\/div>\n\n\n<div class=\"wp-block-buttons is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-fe48e5de wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"\/academy\" target=\"_blank\" rel=\"noreferrer noopener\">Explore Nextcloud Academy<\/a><\/div>\n<\/div>\n\n\n\n<div style=\"height:40px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n<section class=\"faq-section\" id=\"faq\">\r\n\t<div class=\"container\">\r\n\t\t<div class=\"row\">\r\n\t\t\t<div class=\"col-12\">\r\n\t\t\t\t<div class=\"faq-block\">\r\n\t\t\t\t\t<div class=\"text-block\"><h2><span class=\"ez-toc-section\" id=\"faq\"><\/span>FAQ<span class=\"ez-toc-section-end\"><\/span><\/h2><\/div><div class=\"accord-flex\"><div class=\"accords\" id=\"accordion\"><div class=\"card\"><div class=\"card-header\" id=\"heading0\"><button class=\"collapsed\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapse0\" aria-expanded=\"false\" aria-controls=\"collapse0\">Do I need to build my own UI components when developing a Nextcloud app?<\/button><\/div><div id=\"collapse0\" class=\"collapse\" aria-labelledby=\"heading0\" data-bs-parent=\"#accordion\"><div class=\"card-body\"><p>Not if you want a native Nextcloud look and feel. Nextcloud Vue (https:\/\/github.com\/nextcloud-libraries\/nextcloud-vue) provides a set of finished UI components matching the platform\u2019s own design, and a separate package handles CSRF token management automatically, so you don\u2019t need to build that yourself.<\/p>\n<\/div><\/div><\/div><div class=\"card\"><div class=\"card-header\" id=\"heading1\"><button class=\"collapsed\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapse1\" aria-expanded=\"false\" aria-controls=\"collapse1\">Can I publish alpha, beta, or release-candidate versions of my Nextcloud app?<\/button><\/div><div id=\"collapse1\" class=\"collapse\" aria-labelledby=\"heading1\" data-bs-parent=\"#accordion\"><div class=\"card-body\"><p>Yes. Alpha, beta, and release-candidate versions are fully supported. Just include a suffix like \u00ab\u00a0-alpha.1\u00a0\u00bb in the version number within \u00ab\u00a0info.xml\u00a0\u00bb and follow the normal release process. If you\u2019re using GitHub\u2019s release automation to build the tarball, marking it as a pre-release is how Nextcloud distinguishes it from a stable release internally.<\/p>\n<\/div><\/div><\/div><div class=\"card\"><div class=\"card-header\" id=\"heading2\"><button class=\"collapsed\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapse2\" aria-expanded=\"false\" aria-controls=\"collapse2\">Is two-factor authentication available for developer accounts on the Nextcloud App Store?<\/button><\/div><div id=\"collapse2\" class=\"collapse\" aria-labelledby=\"heading2\" data-bs-parent=\"#accordion\"><div class=\"card-body\"><p>Not currently. There\u2019s no dedicated 2FA option for the developer side of apps.nextcloud.com today, though it could see support down the line.<\/p>\n<\/div><\/div><\/div><\/div><div class=\"accords accord2\" id=\"accordion2\"><div class=\"card\"><div class=\"card-header\" id=\"heading3\"><button class=\"collapsed\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapse3\" aria-expanded=\"false\" aria-controls=\"collapse3\">Can I write a Nextcloud app in a language other than PHP?<\/button><\/div><div id=\"collapse3\" class=\"collapse\" aria-labelledby=\"heading3\" data-bs-parent=\"#accordion2\"><div class=\"card-body\"><p>Yes, through ExApps (https:\/\/docs.nextcloud.com\/server\/stable\/developer_manual\/exapp_development\/Introduction.html). ExApps let you write a Nextcloud app\u2019s backend in other languages like Python or Rust while still optionally using the same Vue frontend components. Nextcloud itself uses this pattern for workloads that need real concurrency or async processing, which plain PHP struggles with. For example, the UI for Nextcloud Assistant runs as a normal PHP app, but the actual task processing runs in an ExApp and communicates back via an API once finished, keeping heavy work off the main PHP process.<\/p>\n<\/div><\/div><\/div><div class=\"card\"><div class=\"card-header\" id=\"heading4\"><button class=\"collapsed\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapse4\" aria-expanded=\"false\" aria-controls=\"collapse4\">Can a Nextcloud app run external executables?<\/button><\/div><div id=\"collapse4\" class=\"collapse\" aria-labelledby=\"heading4\" data-bs-parent=\"#accordion2\"><div class=\"card-body\"><p>Technically, yes. Nextcloud\u2019s own antivirus app does exactly this. But it comes with a serious caveat: Giving users any path to execute arbitrary shell commands is a security risk that needs careful handling. Admins retain the ability to configure or restrict this kind of behavior.<\/p>\n<\/div><\/div><\/div><div class=\"card\"><div class=\"card-header\" id=\"heading5\"><button class=\"collapsed\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapse5\" aria-expanded=\"false\" aria-controls=\"collapse5\">Can I get my Nextcloud app added to Nextcloud\u2019s core?<\/button><\/div><div id=\"collapse5\" class=\"collapse\" aria-labelledby=\"heading5\" data-bs-parent=\"#accordion2\"><div class=\"card-body\"><p>Unlikely, and usually unnecessary. Core apps are reserved for functionality essential to a baseline usable experience, like Settings or DAV. For anything more specialized, building an independent app, forking an existing one, or contributing directly gives admins more choice than folding everything into core.<\/p>\n<\/div><\/div><\/div><\/div><\/div>\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t<\/div>\r\n\t<\/div>\r\n<\/section>","protected":false},"excerpt":{"rendered":"<p>Learn how Nextcloud apps are structured under the hood, from APIs and dependency injection to events, and what it takes to publish to the App Store.<\/p>\n","protected":false},"author":60,"featured_media":437979,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"publish_to_discourse":"","publish_post_category":"22","wpdc_auto_publish_overridden":"","wpdc_topic_tags":"","wpdc_pin_topic":"","wpdc_pin_until":"","discourse_post_id":"735326","discourse_permalink":"https:\/\/help.nextcloud.com\/t\/nextcloud-app-development-beginners-guide-what-you-should-know-before-building-your-first-app\/250294","wpdc_publishing_response":"success","wpdc_publishing_error":"","footnotes":""},"categories":[11,6,292,12,1],"tags":[],"class_list":["post-437970","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog","category-community","category-developer-tutorials","category-general","category-uncategorized"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Nextcloud app development beginner&#039;s guide: What you should know before building your first app - Nextcloud<\/title>\n<meta name=\"description\" content=\"Learn how Nextcloud apps are structured under the hood, from APIs and dependency injection to events, and what it takes to publish to the App Store.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/\" \/>\n<meta property=\"og:locale\" content=\"fr_FR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Nextcloud app development beginner&#039;s guide: What you should know before building your first app - Nextcloud\" \/>\n<meta property=\"og:description\" content=\"Learn how Nextcloud apps are structured under the hood, from APIs and dependency injection to events, and what it takes to publish to the App Store.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/\" \/>\n<meta property=\"og:site_name\" content=\"Nextcloud\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/Nextclouders\/\" \/>\n<meta property=\"article:published_time\" content=\"2026-09-29T09:15:18+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-29T09:15:26+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-app-development-beginners-guide-featured-image-1024x576.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1024\" \/>\n\t<meta property=\"og:image:height\" content=\"576\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Kevin Herschbach\" \/>\n<meta name=\"twitter:label1\" content=\"\u00c9crit par\" \/>\n\t<meta name=\"twitter:data1\" content=\"Kevin Herschbach\" \/>\n\t<meta name=\"twitter:label2\" content=\"Dur\u00e9e de lecture estim\u00e9e\" \/>\n\t<meta name=\"twitter:data2\" content=\"16 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/blog\\\/nextcloud-app-development-beginners-guide\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/blog\\\/nextcloud-app-development-beginners-guide\\\/\"},\"author\":{\"name\":\"Kevin Herschbach\",\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/#\\\/schema\\\/person\\\/f3e766d1e7048f3e434902ae8c191593\"},\"headline\":\"Nextcloud app development beginner&rsquo;s guide: What you should know before building your first app\",\"datePublished\":\"2026-09-29T09:15:18+00:00\",\"dateModified\":\"2026-09-29T09:15:26+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/blog\\\/nextcloud-app-development-beginners-guide\\\/\"},\"wordCount\":3227,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/blog\\\/nextcloud-app-development-beginners-guide\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/nextcloud.com\\\/c\\\/uploads\\\/2026\\\/09\\\/nextcloud-app-development-beginners-guide-featured-image.png\",\"articleSection\":[\"Blog\",\"Community\",\"Developer Tutorials\",\"General\",\"Uncategorized\"],\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/nextcloud.com\\\/fr\\\/blog\\\/nextcloud-app-development-beginners-guide\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/blog\\\/nextcloud-app-development-beginners-guide\\\/\",\"url\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/blog\\\/nextcloud-app-development-beginners-guide\\\/\",\"name\":\"Nextcloud app development beginner's guide: What you should know before building your first app - Nextcloud\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/blog\\\/nextcloud-app-development-beginners-guide\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/blog\\\/nextcloud-app-development-beginners-guide\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/nextcloud.com\\\/c\\\/uploads\\\/2026\\\/09\\\/nextcloud-app-development-beginners-guide-featured-image.png\",\"datePublished\":\"2026-09-29T09:15:18+00:00\",\"dateModified\":\"2026-09-29T09:15:26+00:00\",\"description\":\"Learn how Nextcloud apps are structured under the hood, from APIs and dependency injection to events, and what it takes to publish to the App Store.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/blog\\\/nextcloud-app-development-beginners-guide\\\/#breadcrumb\"},\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/nextcloud.com\\\/fr\\\/blog\\\/nextcloud-app-development-beginners-guide\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/blog\\\/nextcloud-app-development-beginners-guide\\\/#primaryimage\",\"url\":\"https:\\\/\\\/nextcloud.com\\\/c\\\/uploads\\\/2026\\\/09\\\/nextcloud-app-development-beginners-guide-featured-image.png\",\"contentUrl\":\"https:\\\/\\\/nextcloud.com\\\/c\\\/uploads\\\/2026\\\/09\\\/nextcloud-app-development-beginners-guide-featured-image.png\",\"width\":1920,\"height\":1080,\"caption\":\"Nextcloud app development beginner's guide: What you should know before building your first app\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/blog\\\/nextcloud-app-development-beginners-guide\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Nextcloud app development beginner&#8217;s guide: What you should know before building your first app\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/#website\",\"url\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/\",\"name\":\"Nextcloud\",\"description\":\"Regain control over your data\",\"publisher\":{\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"fr-FR\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/#organization\",\"name\":\"Nextcloud\",\"url\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/nextcloud.com\\\/c\\\/uploads\\\/2022\\\/10\\\/nextcloud-logo-blue-transparent.svg\",\"contentUrl\":\"https:\\\/\\\/nextcloud.com\\\/c\\\/uploads\\\/2022\\\/10\\\/nextcloud-logo-blue-transparent.svg\",\"width\":\"1024\",\"height\":\"1024\",\"caption\":\"Nextcloud\"},\"image\":{\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/Nextclouders\\\/\",\"https:\\\/\\\/x.com\\\/nextclouders\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/10827569\\\/\",\"https:\\\/\\\/youtube.com\\\/nextcloud\",\"https:\\\/\\\/www.instagram.com\\\/nextclouders\\\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/nextcloud.com\\\/fr\\\/#\\\/schema\\\/person\\\/f3e766d1e7048f3e434902ae8c191593\",\"name\":\"Kevin Herschbach\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/0c71d87b93ea47ae85cda8c9f493bd0aa6763335537bcabe1e492aed84d31c07?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/0c71d87b93ea47ae85cda8c9f493bd0aa6763335537bcabe1e492aed84d31c07?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/0c71d87b93ea47ae85cda8c9f493bd0aa6763335537bcabe1e492aed84d31c07?s=96&d=mm&r=g\",\"caption\":\"Kevin Herschbach\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Nextcloud app development beginner's guide: What you should know before building your first app - Nextcloud","description":"Learn how Nextcloud apps are structured under the hood, from APIs and dependency injection to events, and what it takes to publish to the App Store.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/","og_locale":"fr_FR","og_type":"article","og_title":"Nextcloud app development beginner's guide: What you should know before building your first app - Nextcloud","og_description":"Learn how Nextcloud apps are structured under the hood, from APIs and dependency injection to events, and what it takes to publish to the App Store.","og_url":"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/","og_site_name":"Nextcloud","article_publisher":"https:\/\/www.facebook.com\/Nextclouders\/","article_published_time":"2026-09-29T09:15:18+00:00","article_modified_time":"2026-09-29T09:15:26+00:00","og_image":[{"width":1024,"height":576,"url":"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-app-development-beginners-guide-featured-image-1024x576.png","type":"image\/png"}],"author":"Kevin Herschbach","twitter_misc":{"\u00c9crit par":"Kevin Herschbach","Dur\u00e9e de lecture estim\u00e9e":"16 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#article","isPartOf":{"@id":"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/"},"author":{"name":"Kevin Herschbach","@id":"https:\/\/nextcloud.com\/fr\/#\/schema\/person\/f3e766d1e7048f3e434902ae8c191593"},"headline":"Nextcloud app development beginner&rsquo;s guide: What you should know before building your first app","datePublished":"2026-09-29T09:15:18+00:00","dateModified":"2026-09-29T09:15:26+00:00","mainEntityOfPage":{"@id":"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/"},"wordCount":3227,"commentCount":0,"publisher":{"@id":"https:\/\/nextcloud.com\/fr\/#organization"},"image":{"@id":"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#primaryimage"},"thumbnailUrl":"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-app-development-beginners-guide-featured-image.png","articleSection":["Blog","Community","Developer Tutorials","General","Uncategorized"],"inLanguage":"fr-FR","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/","url":"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/","name":"Nextcloud app development beginner's guide: What you should know before building your first app - Nextcloud","isPartOf":{"@id":"https:\/\/nextcloud.com\/fr\/#website"},"primaryImageOfPage":{"@id":"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#primaryimage"},"image":{"@id":"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#primaryimage"},"thumbnailUrl":"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-app-development-beginners-guide-featured-image.png","datePublished":"2026-09-29T09:15:18+00:00","dateModified":"2026-09-29T09:15:26+00:00","description":"Learn how Nextcloud apps are structured under the hood, from APIs and dependency injection to events, and what it takes to publish to the App Store.","breadcrumb":{"@id":"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#breadcrumb"},"inLanguage":"fr-FR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/"]}]},{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#primaryimage","url":"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-app-development-beginners-guide-featured-image.png","contentUrl":"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-app-development-beginners-guide-featured-image.png","width":1920,"height":1080,"caption":"Nextcloud app development beginner's guide: What you should know before building your first app"},{"@type":"BreadcrumbList","@id":"https:\/\/nextcloud.com\/fr\/blog\/nextcloud-app-development-beginners-guide\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/nextcloud.com\/fr\/"},{"@type":"ListItem","position":2,"name":"Nextcloud app development beginner&#8217;s guide: What you should know before building your first app"}]},{"@type":"WebSite","@id":"https:\/\/nextcloud.com\/fr\/#website","url":"https:\/\/nextcloud.com\/fr\/","name":"Nextcloud","description":"Regain control over your data","publisher":{"@id":"https:\/\/nextcloud.com\/fr\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/nextcloud.com\/fr\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"fr-FR"},{"@type":"Organization","@id":"https:\/\/nextcloud.com\/fr\/#organization","name":"Nextcloud","url":"https:\/\/nextcloud.com\/fr\/","logo":{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/nextcloud.com\/fr\/#\/schema\/logo\/image\/","url":"https:\/\/nextcloud.com\/c\/uploads\/2022\/10\/nextcloud-logo-blue-transparent.svg","contentUrl":"https:\/\/nextcloud.com\/c\/uploads\/2022\/10\/nextcloud-logo-blue-transparent.svg","width":"1024","height":"1024","caption":"Nextcloud"},"image":{"@id":"https:\/\/nextcloud.com\/fr\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/Nextclouders\/","https:\/\/x.com\/nextclouders","https:\/\/www.linkedin.com\/company\/10827569\/","https:\/\/youtube.com\/nextcloud","https:\/\/www.instagram.com\/nextclouders\/"]},{"@type":"Person","@id":"https:\/\/nextcloud.com\/fr\/#\/schema\/person\/f3e766d1e7048f3e434902ae8c191593","name":"Kevin Herschbach","image":{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/secure.gravatar.com\/avatar\/0c71d87b93ea47ae85cda8c9f493bd0aa6763335537bcabe1e492aed84d31c07?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/0c71d87b93ea47ae85cda8c9f493bd0aa6763335537bcabe1e492aed84d31c07?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/0c71d87b93ea47ae85cda8c9f493bd0aa6763335537bcabe1e492aed84d31c07?s=96&d=mm&r=g","caption":"Kevin Herschbach"}}]}},"featured_media_url":"https:\/\/nextcloud.com\/c\/uploads\/2026\/09\/nextcloud-app-development-beginners-guide-featured-image.png","_links":{"self":[{"href":"https:\/\/nextcloud.com\/fr\/wp-json\/wp\/v2\/posts\/437970","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nextcloud.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nextcloud.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nextcloud.com\/fr\/wp-json\/wp\/v2\/users\/60"}],"replies":[{"embeddable":true,"href":"https:\/\/nextcloud.com\/fr\/wp-json\/wp\/v2\/comments?post=437970"}],"version-history":[{"count":21,"href":"https:\/\/nextcloud.com\/fr\/wp-json\/wp\/v2\/posts\/437970\/revisions"}],"predecessor-version":[{"id":438678,"href":"https:\/\/nextcloud.com\/fr\/wp-json\/wp\/v2\/posts\/437970\/revisions\/438678"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/nextcloud.com\/fr\/wp-json\/wp\/v2\/media\/437979"}],"wp:attachment":[{"href":"https:\/\/nextcloud.com\/fr\/wp-json\/wp\/v2\/media?parent=437970"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nextcloud.com\/fr\/wp-json\/wp\/v2\/categories?post=437970"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nextcloud.com\/fr\/wp-json\/wp\/v2\/tags?post=437970"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}