Create a layer
A Pluto layer is a Nuxt layer. It ships source files, and it plugs into the admin with definePlutoExtension.
Layout
A layer has these folders. Use only the ones that you need.
Step 1: the Nuxt config
Name the layer with $meta.name. This name is also the extension id and the i18n namespace.
export default defineNuxtConfig({
extends: ['@plutocms/core'],
$meta: {
name: 'my-layer'
}
})
Extend only the layers that your layer builds on. Each of them already extends its own upstream layers, so you do not list those again.
| Your layer builds on | Put in extends | You get too |
|---|---|---|
| The admin only | @plutocms/core | Nothing |
| Supabase | @plutocms/supabase | @plutocms/core |
For example, @plutocms/supabase-blog extends @plutocms/supabase. It does not extend @plutocms/core, because @plutocms/supabase already does. Add the layers that you extend to the dependencies of the package.json of your layer, with a version range.
An app that uses your layer extends only your layer. See Layers.
css entry that imports tailwindcss or @nuxt/ui. See step 5.Mark a backend layer
A layer that brings its own backend, such as a database and sign-in, must switch the standalone Git CMS off. Set plutoBackend in $meta:
export default defineNuxtConfig({
$meta: {
name: 'my-backend',
plutoBackend: true
}
})
With plutoGithub.enabled: 'auto' (the default), the CMS stays off and logs nothing. With plutoGithub.enabled: true, the module logs an error and stays off. The supabase layer, and the @nuxtjs/supabase module, count as a backend without this marker.
Use the local checkouts of other layers
Your layer reads process.env.PLUTO_<NAME>_PATH || '@plutocms/<name>' for each layer that it extends. The name is in upper case, with - changed to _. For @plutocms/core, the variable is PLUTO_CORE_PATH. For @plutocms/supabase-shop, it is PLUTO_SUPABASE_SHOP_PATH.
const coreLayer = process.env.PLUTO_CORE_PATH || '@plutocms/core'
export default defineNuxtConfig({
extends: [coreLayer]
})
An app sets these variables with PLUTO_LOCAL_ROOT and the plutoLayer helper. See Local layers.
Do not register the @nuxt/eslint module or other development modules in the layer. A module of a layer runs in every app that extends it. Lint the layer with @nuxt/eslint-config in eslint.config.mjs. See Lint.
Step 2: register with definePlutoExtension
Call definePlutoExtension once, from a plugin, with everything that the layer contributes.
export default defineNuxtPlugin(() => {
definePlutoExtension({
id: 'my-layer',
nav: [
{ id: 'reports', label: 'my-layer.nav.reports', icon: 'lucide:chart-bar', to: '/admin/reports' }
],
pages: [
{ id: 'reports', path: '/admin/reports', title: 'my-layer.nav.reports', icon: 'lucide:chart-bar' }
],
capabilities: [
{ id: 'reports-read', key: 'reports:read', label: 'my-layer.capabilities.reportsRead' }
]
})
})
Use a plain id for an entry. Use a plain string in key for a capability. See Extension registry and Permissions.
The page file app/pages/admin/reports.vue is a normal Nuxt page. Core's admin layout wraps every /admin/** page.
Register components of widgets, panels, and banners with defineAsyncComponent. See Lazy loading.
Step 3: add a content type
Declare a content type and register it on the client and the server. See Add a content type.
Step 4: add text
Put the text of the layer in i18n/locales/<code>.json, under one top-level namespace: the $meta.name of the layer. Use pluto.common.* for shared words.
{
"my-layer": {
"nav": { "reports": "Reports" },
"capabilities": { "reportsRead": "Read reports", "group": "Reports" }
}
}
Registry entries hold keys. Core translates them. Add the same keys to every locale file. See Internationalization.
Step 5: add styles
A layer can ship app/assets/css/pluto-layer.css. Core imports it through @plutocms/css.
- Do not import
tailwindcssor@nuxt/uiin it. - Use only
@theme,@custom-variant,@utility, unlayered rules, and imports of plain CSS files next to it. - Add no
@sourcefor theapp/directory. Nuxt UI already scans every layer. - Use Nuxt UI semantic classes in admin code, such as
bg-defaultandtext-muted. See Styles.
Step 6: add a database migration
A layer on the supabase backend keeps its migrations in db/migrations/. Name the files NNN_slug.sql. Grant every new table in the same file. See Migrations and Data API grants.
Step 7: publish
A Nuxt layer ships its source. There is no build step for a layer package. Pluto layers use this pipeline:
- Conventional Commits.
- release-please.
- A CI gate.
- npm trusted publishing.
See Releasing. Set name, version, license, description, publishConfig: { "access": "public" }, and a repository field in package.json. npm provenance refuses to publish without the repository field.