seo fields - BaryoDev/barakoCMS GitHub Wiki
SEO fields
A content type has no meta title, meta description, canonical URL or social image until it asks for them. Without a shared set, every agency invents its own naming convention and every frontend re-implements the tags against a different one.
Opting a content type in
POST /api/content-types/{name}/seo-fields
Adds five fields to the type. Additive and idempotent: a field the type already has is reported and left exactly as it was, because a client may well have renamed the display name or made one required, and none of that is this endpoint's to undo.
| Field | Type | What it is |
|---|---|---|
MetaTitle |
string | What a search result and a browser tab say |
MetaDescription |
text | The snippet under the title |
CanonicalUrl |
url | The one URL this content should be indexed under |
SocialImage |
url | The sharing image |
NoIndex |
bool | Ask search engines to skip this entry |
They are ordinary fields marked Public, not a separate structure. That is what makes the delivery side nearly free: they are validated, delivered, searched and scrubbed by everything that already handles a field.
All five are optional. Opting a type in must not make every existing entry invalid, and the fallbacks below are what make an empty value fine.
Reading them
Every public delivery response carries a resolved seo block, or omits it entirely when the type has
not opted in.
{
"id": "...",
"slug": "spring-roast-notes",
"data": { "...": "..." },
"seo": {
"title": "Spring roast notes | Barako Coffee",
"description": "What changed in the March lot.",
"canonicalUrl": null,
"imageUrl": "https://cdn.example.com/roast.jpg",
"noIndex": false
}
}
Resolved rather than left to the caller, and that is the point of it existing. The raw fields are
already in data; a frontend reading them itself would have to know the names and re-implement the
fallback, and two frontends would do it two ways.
Null rather than an empty object when the type has not opted in, so a caller cannot mistake "this type has no SEO fields" for "this entry has not filled them in".
The fallback
An unset meta title resolves to the entry's own title. An empty title tag is worse than no tag: a search engine shown one indexes the page with nothing to display, whereas a page with no tag gets a title chosen from its content.
The field the type gives the title role is read first, when it has one and the entry holds a
value in it (see field-hints-and-roles.md). Failing that, the entry
title is found under Title, Name, DisplayName, Label, Subject or Heading, in
that order, which is the list the admin uses to label an entry.
The role is read here and by the feed only. The file usage list (BarakoCMS.Files) and the AI
module still find a title by name, each with its own list. So on a type whose title role sits on a
field outside those lists, the SEO title and those labels can differ: the first is the field the
type names, the others are whatever their name list finds, or nothing.
Only a genuinely titleless entry resolves to null, and a null is absent from the JSON, so a
frontend renders no tag at all.
A meta title of only spaces counts as unset. An editor clearing the box leaves an empty string rather than a missing key.
Lengths
MetaTitle is usually cut around 60 characters and MetaDescription around 155. Those are
guidance, not validation, and deliberately not enforced: search engines truncate on pixel width
rather than character count, so a hard limit would be wrong in both directions, refusing a title that
displays fine and accepting one that does not. The admin shows the count and a preview; the decision
stays with the person who can see the words.
NoIndex and the sitemap
An entry with NoIndex set is left out of /api/public/sitemap.xml.
The tag on the page is the instruction a crawler obeys. The sitemap is the invitation. Listing a page and then telling the crawler to go away when it arrives wastes its budget on the site, and Search Console reports the contradiction as an error, which reads as a broken sitemap rather than a deliberate choice.
It is still delivered through the API, because the frontend needs it to render the robots tag.
Generated from docs/seo-fields.md by scripts/wiki-sync.sh. Edit the doc in the repository, not this page.