-
Notifications
You must be signed in to change notification settings - Fork 6
blueprints
Every new site used to start from an empty schema, and the first hour was retyping the same content types. A blueprint is a named set of content type definitions that one call creates in a tenant.
GET /api/content-types/blueprints
POST /api/content-types/blueprints/{name}
Both are gated on manage_content_types, the capability that creates a content type, because that
is what applying one does. The list returns each blueprint's name, description, the type names it
creates, whether it is built in, and any errors found in its file. The apply creates every type in
the caller's tenant and returns their ids and names.
Applying is additive and all or nothing. If any type the blueprint declares already exists in the
tenant, the whole apply is refused with 409 and the refusal names the types that clash. Nothing is
replaced, ever. A partial apply would leave references pointing at a type whose fields are not the
ones the blueprint assumed, so one clash refuses the set. Types that the blueprint does not mention
are left alone, so applying blog to a tenant that already has a newsletter type works.
Types are created the way POST /api/content-types creates one: name normalized, document sourced,
the sourcing decision recorded against the name. A name that was decided event sourced before is
refused rather than recreated, for the reason event-sourced-content-types.md
gives.
The audit log records contenttype.blueprint_applied with the names created.
Every addressable type has a Slug field of type slug, which is what makes
/api/public/{type}/{slug} exist, and every type is publicly deliverable. The site type is the
exception to the slug: it is a singleton, one entry per tenant, read from the list. Fields that are for the
team and not the public are marked Sensitive (masked on the way out) or Hidden (removed).
| Blueprint | Types | Notes |
|---|---|---|
blog |
post, category, author, page
|
Post has a markdown body, excerpt, cover image URL, published date, author and category references, tags. Author email is Sensitive. Page can nest under a parent page and has an optional HideTitle bool, which tells the renderer to leave the page's title off. |
events |
event, venue, speaker
|
Event has starts and ends, a venue reference and a geopoint location, so filter[Location][near] works on delivery. Venue contact details are Sensitive. |
portfolio |
project, client
|
Project has a client reference, a gallery array, a live URL and a testimonial. Client contact name and email are Sensitive, internal notes are Hidden. |
docs |
article, section
|
Article has a markdown body, a required section reference and an order within it. Section can nest under a parent section. |
site |
site |
A singleton holding the site's identity, theme and chrome, read by the renderer from /api/public/site. The theme and chrome fields are JSON; site-settings.md gives their shapes. |
devsite |
page, post, category, author, doc, package, release, contributor, up-for-grabs, milestone
|
A product site: the blog types under their own names, plus a flat doc type carrying its own section, order and parent fields for a documentation tree, and five types meant to be filled by collection syncs (#794) rather than typed by hand: package (NuGet), release and contributor (GitHub), up-for-grabs (open issues), milestone (open GitHub milestones, one per product). The shape barakocms.com runs on (BaryoDev/barakoCMS#959); see below for wiring it into a site's Collections setting. Its page has the same optional HideTitle as blog. |
The blueprints carry no SEO fields. Run POST /api/content-types/{name}/seo-fields on the types a
frontend renders as pages; see seo-fields.md.
Every body field above is markdown, deliberately. The richtext type is still valid and is still
accepted, and neither type is sanitised: both store and return the string that was saved. The
difference is what a consumer does with it. Markdown is normally rendered by something that drops
raw HTML, which makes an untrusted body harmless. Richtext exists to be rendered as HTML, so a
blueprint that chose it would hand anyone who can edit content a script tag on every page that shows
it. Choose richtext only where the deployment sanitises on its own side.
There is no media content type in the core, so an image is a url field. If a deployment models
media as content, a custom blueprint can reference it instead.
Applying devsite only creates the types. A renderer reads them as lists and detail pages through
the site's Collections setting, in the shape site-settings.md
documents. post, author and category need no entry: their field names already match
barakoPress's own defaults, the same way applying blog needs none. doc, package, release,
contributor, up-for-grabs and milestone do, because a renderer has no default for a type it did
not name:
{
"doc": {
"type": "doc",
"route": "/docs",
"fields": { "title": "Title", "slug": "Slug", "summary": "Summary", "body": "Body" },
"tree": { "section": "Section", "order": "Order", "parent": "ParentDoc", "product": "Product", "editPath": "EditPath" }
},
"package": {
"type": "package",
"route": "/modules",
"fields": { "title": "Name", "slug": "Slug", "summary": "Summary", "image": "IconUrl", "url": "Url" }
},
"release": {
"type": "release",
"route": "/changelog",
"fields": { "title": "Title", "slug": "Slug", "body": "Body", "date": "PublishedAt", "url": "Url" },
"sort": "-PublishedAt"
},
"contributor": {
"type": "contributor",
"fields": { "title": "Name", "slug": "Slug", "photo": "Photo", "url": "ProfileUrl" },
"index": false
},
"up-for-grabs": {
"type": "up-for-grabs",
"fields": { "title": "Title", "slug": "Slug", "summary": "Summary", "tags": "Tags", "url": "Url" },
"index": false
},
"milestone": {
"type": "milestone",
"fields": { "title": "Version", "slug": "Slug", "summary": "Description", "url": "Url" },
"index": false
}
}release and contributor both feed the changelog page, contributor and up-for-grabs both feed
the community page, and milestone feeds the roadmap page grouped by product. None of those three
pages is one collection's index: each composes from blocks that draw on one or two collections, the
way any page of blocks does. index: false on contributor, up-for-grabs and milestone keeps
them out of a bare listing that no site has ever linked to, while leaving them fully readable by key
for that composition.
Every field named above is a field the collection sync in collection-syncs.md
can write to directly: package.Downloads, release.PublishedAt, contributor.Contributions,
up-for-grabs.RepositoryName and milestone.Product/Open/Closed are ordinary data fields with no
renderer role, there for the sync to fill and for a custom block to read, but not required by the
shapes above. milestone has no date field: the roadmap page on barakocms.com shows a version, a
description, an open and a closed count and a link, grouped by product, and says why in its own
copy: "There are no dates here on purpose... a date would be a guess presented as a
commitment." Reading what the page actually renders, rather than guessing at a shape, is why this
type has no PublishedAt.
Set Blueprints:Path to a directory. Every *.json file in it is listed alongside the built-ins and
applies the same way. The setting is unset by default, and the directory is read on every list and
apply, so a file dropped in is visible without a restart. At most 100 files are read; past that the
list says so under problems.
A file is one object:
{
"name": "agency",
"description": "Case studies and the people who wrote them.",
"contentTypes": [
{
"name": "case-study",
"displayName": "Case study",
"isPubliclyDeliverable": true,
"fields": [
{ "name": "Title", "displayName": "Title", "type": "string", "isRequired": true },
{ "name": "Slug", "displayName": "Slug", "type": "slug", "isRequired": true },
{ "name": "Lead", "displayName": "Lead", "type": "reference", "referenceType": "consultant" }
]
},
{
"name": "consultant",
"displayName": "Consultant",
"fields": [
{ "name": "Name", "displayName": "Name", "type": "string", "isRequired": true },
{ "name": "Rate", "displayName": "Day rate", "type": "money", "sensitivity": "Hidden" }
]
}
]
}The entries under contentTypes are the same shape the Portability import accepts, so a file can be
assembled from GET /api/portability/export by keeping the types and dropping the contents.
A file is validated when it is listed, with the validator the create endpoint runs, plus three rules of its own:
- the name is lower-case letters, digits and hyphens, and a name a built-in already uses is refused;
- a reference must point at a type declared in the same blueprint, so applying on an empty tenant produces a schema that works;
- a property the type does not have is an error rather than ignored, so a misspelt
sensitivitycannot leave a field Public that the author marked Hidden.
An invalid file still appears in the list, with its problems under errors, and applying it is a
400 repeating them. A file that does not parse is listed under its file name with the parse error.
Generated from docs/blueprints.md by scripts/wiki-sync.sh. Edit the doc in the repository, not this page.
Releases
Start here
- Approval by configuration
- Configuring email
- Delivering a client project on barakoCMS
- Deploying barakoCMS on a VM
- Deploying barakoCMS on a managed platform
- Upgrading from 3.x to 4.0
- Your first module
Content
- Content type blueprints
- Choice fields
- Pushing entries to a collection
- Collections filled from outside
- Public delivery API
- Event-sourced content types
- Image variants
- Scheduling publish, unpublish and sensitivity
- SEO fields
- Site settings
- URL redirects
Security and access
- Security and compliance posture
- Scanning uploads for malware
- Where the admin keeps your session, and why
Tenancy
Operations