Profiles
Nick uses a modular architecture where functionality is organized into profiles. Each project can have multiple active profiles that add or change functionality. Profiles are registered in config.ts and applied in order.
π¦ src/profiles/my_profile/
βββ metadata.json # Profile metadata (id, title, version)
βββ index.ts # Runtime registration (models, seeds, routes, etc.)
βββ catalog.json # Catalog indexes and metadata
βββ roles.json # Role definitions
βββ permissions.json # Permission definitions
βββ actions.json # Action definitions
βββ types/ # Content type definitions
βββ behaviors/ # Behavior definitions
βββ controlpanels/ # Control panel configurations
βββ workflows.json # Workflow definitions
βββ users.json # User definitions
βββ groups.json # Group definitions
βββ vocabularies/ # Vocabulary definitions
βββ redirects.json # Redirect definitions
βββ seeds.json # Seed data
βββ upgrades/ # Incremental upgrade steps
βββ 1001/
βββ 1002/
βββ ...
Registration
Profiles are listed in the profiles array in config.ts. The name before the colon resolves to the Node package name; the part after the colon is the folder name of the profile.
export const config = {
profiles: [
'@plone/nick:core',
'my_nick_project:default',
],
};
Metadata
Each profile has a metadata.json file that identifies it and tracks its version:
{
"id": "@plone/nick:core",
"title": "Nick Core",
"description": "Core functionality of Nick",
"version": 1006
}
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier for the profile. |
title | string | Human-readable name of the profile. |
description | string | Brief description of the profileβs role. |
version | number | Current version number for upgrade tracking. |
Runtime registration (index.ts)
When Nick starts, each profileβs index.ts module is loaded and its init() function is called. This is where the profile registers its models, seeds, blocks, events, vocabularies, middleware, routes, behaviors, content rules, and scheduled jobs. The initProfiles helper (src/helpers/profiles/profiles.ts) iterates through profiles in order and calls init() on each.
Configuration files
Profiles contain JSON files that define database records. When pnpm seed runs these files are read by seed handlers and applied to the database. Each file corresponds to a specific domain:
| File | Seed handler | Description |
|---|---|---|
metadata.json | seedProfile | Profile record (version tracking). |
permissions.json | seedPermission | Permission definitions. |
roles.json | seedRole | Roles with assigned permissions. |
actions.json | seedAction | Action and button definitions. |
catalog.json | seedCatalog | Catalog indexes and metadata columns. |
types/ | seedType | Content type definitions. |
behaviors/ | seedBehavior | Behavior definitions. |
controlpanels/ | seedControlpanel | Control panel configurations. |
workflows.json | seedWorkflow | Workflow definitions. |
users.json | seedUser | User definitions. |
groups.json | seedGroup | Group definitions. |
vocabularies/ | seedVocabulary | Vocabulary definitions. |
redirects.json | seedRedirect | Redirect definitions. |
documents/ | seedDocument | Document content with version blocks. |
contentrules.json | seedContentRule | Content rule definitions. |
scheduledjobs.json | seedScheduledJob | Scheduled job definitions. |
Each seed handler checks whether its corresponding file or directory exists at the given profile path. If it does, the data is imported; otherwise it is silently skipped. This means the same seed logic is reused for both initial profile installation and incremental upgrades.
Seeding
The pnpm seed command (scripts/seed.ts) processes every registered profile. For each profile it:
- Reads the profileβs
metadata.json. - Queries the database for the currently installed version.
- If the profile is not yet installed (or the version in metadata is newer), it calls
seeds.run(trx, profilePath), which passes the profile path to every registered seed handler.
Status
The pnpm seed:status command displays a table of all registered profiles, their currently installed version, and the latest version available in metadata.json:
Profile Current Latest
@plone/nick:core 1004 1006
my_nick_project:default 1000 1000
This is useful for checking which profiles need upgrades before running pnpm seed:upgrade.
Upgrades
As a profile evolves, you add upgrade steps to migrate existing installations. Upgrades live in numbered subdirectories under upgrades/:
π¦ upgrades/
βββ 1001/
β βββ metadata.json
β βββ controlpanels/security.json
βββ 1002/
β βββ metadata.json
β βββ permissions.json
β βββ roles.json
βββ 1006/
β βββ metadata.json
β βββ catalog.json
Upgrade metadata
Each upgrade directory contains a metadata.json describing the change:
{
"id": "@plone/nick:core",
"title": "Nick Core",
"description": "Core functionality of Nick",
"version": 1006,
"upgrade": "Add position in parent catalog index."
}
The upgrade field is a human-readable description of what the step does.
Upgrade files
Alongside metadata.json, an upgrade directory contains the same JSON configuration files used during initial seeding. Only the files relevant to the upgrade need to be present. For example, an upgrade that adds new permissions and roles:
upgrades/1002/permissions.json:
{
"purge": false,
"permissions": [
{
"id": "View Comments",
"title:i18n": "View Comments"
},
{
"id": "Add Comment",
"title:i18n": "Add Comment"
}
]
}
upgrades/1002/roles.json:
{
"purge": false,
"roles": [
{
"id": "Anonymous",
"title:i18n": "Anonymous",
"permissions": ["View Comments"]
},
{
"id": "Authenticated",
"title:i18n": "Authenticated",
"permissions": ["View Comments", "Add Comment"]
}
]
}
Running upgrades
Use the pnpm seed:upgrade command to apply pending upgrades. The upgrade logic in scripts/seed.ts works as follows:
- Compare versions: For each profile, the current installed version (from the database) is compared against the latest version in
metadata.json. - Calculate gap: If the installed version is behind, the difference determines how many upgrade steps to apply.
- Sequential execution: Each missing step is applied in order by calling
seeds.run(trx, ".../upgrades/<version>")for each version number. For example, if the installed version is1003and the latest is1006, upgrades1004,1005, and1006are applied sequentially. - Idempotent seed handlers: Each seed handler checks for the presence of its corresponding file inside the upgrade directory. If the file exists, the changes are applied; if not, the handler skips. This means an upgrade step only needs to include the files that actually change.
If any step fails, the entire transaction is rolled back, ensuring consistency.
Creating a new upgrade
To add a new upgrade step:
- Create a new directory under
upgrades/named after the next version number (e.g.,1007). - Add a
metadata.jsonwith the profile id, version, and a description of the change. - Add only the JSON configuration files that need to change (e.g.,
catalog.json,permissions.json,roles.json,controlpanels/, etc.). - Update the profileβs root
metadata.jsonto reflect the new version number.
The seed handlers will pick up the new files automatically β no manual registration is required.