Authors
A post can have one or more authors. This works on both backends (the Git CMS and Supabase), on a server and on a static site. Readers get a byline, an author box and a page for each author: /authors/<slug>.
An author is a content type. A post stores the ids of its authors in a reference field with multiple. The order of the list is the order of the byline. The author pages are the public API with a filter.
The blog template has the author pages already.
The authors
Each backend registers the content type author. An author has these fields:
| Field | Type | Use |
|---|---|---|
name | text | The title field. Shown as Name. |
slug | slug | Made from the name. |
bio | textarea | A short text about the author. |
avatar | media | An image. The picker accepts images only. |
An author has no status. An author is public when you save it.
An author has no link to a user account. So no email address and no role go to the public site. A guest author or a pen name is a normal author.
| Git CMS | Supabase | |
|---|---|---|
| Where an author lives | content/authors/<slug>.md | The table authors |
| Where a post keeps its authors | A YAML list of slugs in the front matter | The column posts.author_ids (bigint[]) |
| Id of an author | The slug (the file name) | The number id of the row |
| Set up | Nothing. The type registers with postType. | Run the migration 008_post_authors.sql of @plutocms/supabase-blog |
Git CMS
An author is a Markdown file with front matter only. A post lists the slugs of its authors:
---
name: Ana Lima
bio: Ana writes about trails and weather.
avatar: ana-lima-k3j9a
---
---
title: A weekend in the hills
authors:
- ana-lima
---
- The file has
name,bio,avatarand the timestamps. - The type registers when
plutoGithub.postTypeis on, which is the default. WithpostType: false, the type is not there. - A rename of an author (a new slug) rewrites every post that uses it, in the same commit as the rename.
- Remove moves an author to the trash. The posts keep the author. Delete forever, or the purge after 30 days, removes the author from the posts, in the same commit. If the list is then empty, Pluto removes the
authorskey. - If a post changes while the commit runs, the save answers 409.
Supabase
Migration 008_post_authors.sql of @plutocms/supabase-blog adds:
- the table
authors, with the columnsid,created_at,updated_at,name,slug(unique),bioandavatar; - the column
author_idsonposts, abigintarray in byline order; - the policies, the grants and a trigger. When an author is deleted, the trigger removes its id from every post.
Remove moves an author to the trash (migration 009_trash.sql). The posts keep its id. Delete forever, or the purge after 30 days, deletes the row, and the trigger runs.
Anyone can read the authors that are not in the trash. A write needs authors:manage.
Apply the migration from /admin/migrations. A post save fails until the migration runs. See Migrations.
Who can manage authors
Creating, editing and deleting an author need the authors:manage capability.
- Git CMS: every user with push access has it.
- Supabase: when it runs, migration 008 gives
authors:manageto each role that holdsposts:publish. Give it by hand to a role that you add later. An admin holds all capabilities.
A user without authors:manage can still pick the existing authors in a post.
In the admin
The admin menu has an Authors item. It opens the list of authors and the form of an author.
The post form
The side panel of the post form has the field Authors. It is a multi-select with a search box. A selected author is a chip. The order of the chips is the order of the byline.
To add an author that does not exist, type the name and select Create "…". Pluto creates the author with the name and a slug made from it, then selects it. The option shows only for a user with authors:manage.
The field has remember: true. A new post gets the authors that the user saved last time. Pluto keeps this list for each browser.
- An author that does not exist anymore is not filled.
- A new device fills nothing until the first save there.
Read authors in the public API
The author is a public type, so it has its own routes:
| Route | Answer |
|---|---|
GET /api/pluto/v1/content/author | The authors. |
GET /api/pluto/v1/content/author/<slug> | One author. |
An author is { id, name, slug, bio, avatar, createdAt, updatedAt }. avatar is a media record, or null.
A post answer has its authors in place of the ids. Each author is { id, label, slug, avatar, bio }:
{
"id": "a-weekend-in-the-hills",
"title": "A weekend in the hills",
"authors": [
{
"id": "ana-lima",
"label": "Ana Lima",
"slug": "ana-lima",
"avatar": { "id": "ana-lima-k3j9a", "url": "/media/ana-lima.webp", "width": 400, "height": 400, "alt": "" },
"bio": "Ana writes about trails and weather."
}
]
}
- A post with no author has
[]. - An author that does not exist anymore is not in the answer.
avatarandbiocome from the optionpublicFieldsof the field. See Reference fields.
To list the posts of an author, filter by the slug or by the id:
curl "https://your-site.dev/api/pluto/v1/content/post?filter[authors]=ana-lima"
A slug or an id that matches no author gives an empty list. The filter works on a static site too. See Public API.
Author pages in a Nuxt site
Two composables read the authors: useAuthors and useAuthor. useAuthors sorts by name. A post of usePosts has authors.
<script setup lang="ts">
const route = useRoute()
const { author } = await useAuthor(() => String(route.params.slug))
const { posts } = await usePosts({
filter: { authors: String(route.params.slug) }
})
</script>
<template>
<h1>{{ author?.name }}</h1>
<p>{{ author?.bio }}</p>
<PostList :posts="posts" />
</template>
useAuthors(options)returnsauthors.useAuthor(slug)returnsauthor. A missing author shows the 404 page.- The types are
PlutoAuthor,PlutoPostAuthorandPlutoPostSummary.authors.
The blog template has these pages:
| Page | Content |
|---|---|
/authors/<slug> | An author with the avatar, the bio and the posts. |
/posts/<slug> | A byline under the title, and an author box after the text. |
The preview link
An author has a View link in the admin. Its previewUrl is /authors/<slug>. If your site uses another path, change it with extendContentTypes. See Extend a content type of another layer.
export const authorPathsExtension: PlutoContentTypeExtension = {
id: 'author-paths',
extend: type =>
type.name === 'author'
? { ...type, previewUrl: item => (item.slug ? `/team/${item.slug}` : undefined) }
: type
}
Register it on the client with definePlutoExtension and on the server with extendContentTypes, as in the guide above.
The authors field
The post type has this field:
{
name: 'authors',
type: 'reference',
label: 'Authors',
target: 'author',
multiple: true,
create: true,
remember: true,
publicFields: ['avatar', 'bio'],
region: 'side'
}
createcreates an author from the field. The user needsauthors:manage.rememberfills a new post with the last saved value.publicFieldsaddsavatarandbioto each author in the public API.
SEO
With the SEO add-on, the pages get the author tags: <meta name="author">, article:author, JSON-LD and <dc:creator> in the feed.
Limits
- An author has no link to a user account.
- The post form loads up to 1000 authors. A blog with more authors needs a server search, which Pluto does not have yet.
- On the Git CMS, a rename reads every post file. On a large blog, one commit writes many files.
- On GitHub, an inline create is its own commit and its own build. The next save can cancel the build before it ends.
Tags and categories
Add tags and categories to posts on both backends, read them from the public API, and build the term pages of a site.
Git CMS
Store Markdown content in a Git repository. Edit it in the Pluto admin. It works with local Git out of the box, and with GitHub to publish a deployed site. No database.