Module extensions
A module is a top-level area of the admin app. CairnCMS ships six built-in modules: Content, User Directory, File Library, Insights, Settings, and Activity (the last is registered with hidden: true and reached via the Activity Log link in the sidebar rather than the module bar). A module extension adds a new one.
Modules are the broadest extension type. Where an interface affects a single field and a layout affects a single collection page, a module is an entire workspace with its own routes, navigation, and page structure. Reach for a module when none of the existing surfaces fit and you need a custom area inside the app.
A module extension is a single npm package created by the extensions toolchain. It registers a set of routes, each rendered by a Vue component.
Anatomy
Section titled “Anatomy”The package’s source entrypoint exports a configuration object built with defineModule:
import { defineModule } from '@cairncms/extensions-sdk';import ModuleComponent from './module.vue';
export default defineModule({ id: 'custom', name: 'Custom', icon: 'box', routes: [ { path: '', component: ModuleComponent, }, ],});defineModule is a no-op type wrapper; it returns the config unchanged but gives you full TypeScript inference on the shape.
Configuration
Section titled “Configuration”The fields available on a module configuration object:
id— unique key. Also serves as the base path for the module’s routes (/<id>reaches its root). Scope proprietary modules with an author or organization prefix.name— display name shown in tooltips and accessible labels.icon— icon name from the Material icon set or one of CairnCMS’s custom icons. Shown as the module’s button in the module bar.color— optional accent color for the icon.routes— array of Vue Router route records describing the pages inside the module.hidden— whentrue, the module is registered but never appears in the module bar. Useful for modules that exist only to be linked into from elsewhere (or for routes that need to live outside any visible module).preRegisterCheck— optional sync or async function that gates whether the module is registered for the current user. See below.
Routes
Section titled “Routes”The routes array is a Vue Router route table. CairnCMS mounts it under /<module-id>, so paths inside the array are relative and should not start with a slash.
The button in the module bar links to /<module-id>, so the routes array should always include a root route with path: '':
routes: [ { path: '', component: OverviewView, }, { path: 'detail/:id', component: DetailView, props: true, },];Nested children, named routes, redirects, and other Vue Router features all work normally. See the Vue Router documentation for the full route record shape.
Route components
Section titled “Route components”Each route’s component is a Vue component that renders into the main page area inside the module — the space framed by the module bar, navigation pane, header, and sidebar. To pick up that page chrome, wrap the component’s content in the globally-registered private-view element:
<template> <private-view title="My Custom Module"> <p>Content goes here.</p> </private-view></template>private-view accepts slots for headline, title-outer:prepend, actions, navigation, and sidebar which is the same slot pattern every built-in module uses. Without private-view, the route renders raw inside the page area but loses the page chrome.
Adding the module to the module bar
Section titled “Adding the module to the module bar”A registered module does not appear in the module bar automatically. The operator must add it through Settings > Project Settings > Modules:
- Open Project Settings.
- In the Modules section, click Add and choose the new module.
- Toggle it on and save.
Registering a module makes it available, but operators decide which modules show in the bar. To bypass this entirely (for a hidden helper module, for example), set hidden: true on the config.
preRegisterCheck
Section titled “preRegisterCheck”preRegisterCheck is an optional function that runs before a module is registered for the current user. It receives the user record and the user’s permissions, and returns a boolean (or a promise that resolves to one) indicating whether the module should be available.
import { defineModule } from '@cairncms/extensions-sdk';
export default defineModule({ id: 'custom', name: 'Custom', icon: 'box', routes: [/* ... */], preRegisterCheck(user, permissions) { if (user.role.admin_access) return true;
return permissions.some( (p) => p.collection === 'directus_files' && p.action === 'read' ); },});Use this to scope module visibility to a role or permission set. Returning false means the module is not registered for the current user. The route is unreachable and the entry never appears in the module bar.
Accessing internal systems
Section titled “Accessing internal systems”The SDK exports three composables for reaching internal systems from a module’s route components:
useApi()— an axios instance pre-configured to talk to the CairnCMS API as the current useruseStores()— the Pinia stores the app uses internallyuseExtensions()— the registered-extension catalog
import { useApi, useStores } from '@cairncms/extensions-sdk';
export default { setup() { const api = useApi(); const { useCollectionsStore } = useStores(); const collectionsStore = useCollectionsStore(); // ... },};All three work in any Vue 3 setup, including Options-API components that pair an options block with a small setup() function.
A complete minimal example
Section titled “A complete minimal example”A module that lists every collection in the project.
src/index.js:
import { defineModule } from '@cairncms/extensions-sdk';import ModuleComponent from './module.vue';
export default defineModule({ id: 'collections-overview', name: 'Collections Overview', icon: 'list_alt', routes: [ { path: '', component: ModuleComponent, }, ],});src/module.vue:
<template> <private-view title="Collections Overview"> <ul> <li v-for="collection in collections" :key="collection.collection"> {{ collection.collection }} </li> </ul> </private-view></template>
<script setup>import { useStores } from '@cairncms/extensions-sdk';import { computed } from 'vue';
const { useCollectionsStore } = useStores();const collectionsStore = useCollectionsStore();
const collections = computed(() => collectionsStore.collections);</script>Build with npm run build and install or symlink the package. Then enable the module in Settings > Project Settings > Modules to make it appear in the module bar.
Where to go next
Section titled “Where to go next”- Endpoints cover the API-side counterpart for modules that need their own server routes.
- Hooks cover server-side reactions to platform events; useful when a module needs to listen for or modify platform behavior.
- Creating extensions covers the toolchain in full.