Entry Types
An entry type is a registered kind of entry. You name one with registerEntryType, and the admin screens, the public URLs and the capability set all follow from the options you pass it.
Overview
Section titled “Overview”registerEntryType(name, options) takes the name that lands in the entry’s type column, plus the options that tell the rest of the site how to treat those rows. Only label is required, and the defaults behind the rest are conservative. A type carrying nothing but a label is public and flat, and has no archive route. supports is the one omission that still turns something on. Leave it out and the type gets the block editor canvas, but no title field, no excerpt field, no revisions and no autosave.
Nothing in the database changes when you register a type. Entries of every type share one entries table and are told apart by that type column, so a slug is unique per type rather than site-wide, and two types can both hold a sicilian-caponata.
Registration happens inside a plugin’s setup, which Overview explains in full. Registering the same name twice throws at boot rather than letting the second registration win.
Quickstart
Section titled “Quickstart”-
Register the type. Create
plugins/recipes.ts:import { definePlugin } from "plumix/plugin";export const recipes = definePlugin("recipes", {setup: (ctx) => {ctx.registerEntryType("recipe", {label: "Recipes",labels: { singular: "Recipe", plural: "Recipes" },supports: ["title", "editor", "excerpt", "revisions", "autosave"],hasArchive: true,rewrite: { slug: "recipes" },});},}); -
Install it. In
plumix.config.ts, importrecipesfrom./plugins/recipesand add it to thepluginsarray. -
Start the dev server.
Terminal window pnpm devRecipes appears in the admin sidebar, listed at
/_plumix/admin/entries/recipes. Publish Sicilian Caponata and it serves at/recipes/sicilian-caponata, listed on an archive at/recipes.
Labels
Section titled “Labels”labels.plural is the sidebar string, and label is what the sidebar falls back to when plural is absent. labels holds the rest of the admin chrome, and every key in it is optional. Where one is missing the admin falls back to a generic noun-free string, so addNewItem renders as “Add” rather than “Add Recipe”.
Every label accepts either a plain string or a Lingui message descriptor. A string is the right choice for a site that ships in one language; a descriptor is what makes the string translatable.
import { definePlugin } from "plumix/plugin";
export const recipes = definePlugin("recipes", { setup: (ctx) => { ctx.registerEntryType("recipe", { label: "Recipes", labels: { singular: "Recipe", plural: "Recipes", addNewItem: "Add Recipe", editItem: "Edit Recipe", searchItems: "Search recipes…", notFound: "No recipes yet", loadErrorItems: "Couldn’t load recipes", moveToTrash: "Move recipe to trash?", }, }); },});labels.plural reaches further than the other keys. It is the string the sidebar renders, and the manifest build also slugifies it into the admin URL segment, so “Recipes” gives /_plumix/admin/entries/recipes. Leave plural out and the sidebar falls back to label while the slug falls back to the type name plus an s, which is fine for recipe and wrong for anything with an irregular plural. Two types whose plurals slugify to the same string fail the manifest build rather than shadowing each other in the admin router. A taxonomy works the other way round, and its sidebar item reads label rather than labels.plural.
There are 19 label keys in all. The full list, with what each one renders, belongs on the entry type reference page, which is not written yet.
Hierarchy
Section titled “Hierarchy”isHierarchical: true gives entries of the type a parent. The editor grows a parent picker, the router matches nested URLs through a catch-all segment instead of a single slug, and the permalink builder walks the parentId chain to assemble the path. That is how page, from the pages plugin, serves /about/team.
Reparenting is guarded on the way in. A parent must be an entry of the same type that the caller can already read, and the server walks the chain upward before writing, so it rejects A under B under A rather than storing it. The ancestor walk that builds a URL stops at 50 levels.
rewrite.isHierarchical: false keeps the flat single-segment URL while the data stays a tree. Reach for it when the parent groups entries in the admin but should not appear in the address.
Recipes are flat, so recipe leaves both alone.
Archives and URLs
Section titled “Archives and URLs”Two options decide the public shape of a type.
rewrite.slug sets the URL prefix. With rewrite: { slug: "recipes" } a single recipe serves at /recipes/:slug. Drop rewrite and the prefix falls back to the type name, giving /recipe/:slug. Set it to the empty string and the type claims the site root, which is what the pages plugin does; the router sorts those root-level patterns after every other auto-generated rule so a page cannot swallow another type’s URLs. Apart from that empty string, rewrite.slug has to be a single lowercase path segment: a / or URL-pattern syntax such as ":anything" or "*" throws at boot rather than compiling into a route that shadows every other type’s URLs.
hasArchive decides whether a listing route exists at all, and it is off unless you ask for it. true mounts the archive at the same prefix the singles use, so /recipes and /recipes/page/2. A string mounts it somewhere else, and has to be a single lowercase path segment.
import { definePlugin } from "plumix/plugin";
export const recipes = definePlugin("recipes", { setup: (ctx) => { ctx.registerEntryType("recipe", { label: "Recipes", labels: { singular: "Recipe", plural: "Recipes" }, hasArchive: "cookbook", rewrite: { slug: "recipes" }, archivePerPage: 12, }); },});That registration serves single recipes under /recipes/ and lists them at /cookbook, twelve to a page. archivePerPage defaults to 20.
An archive lists entries that are published and carry a publish time. A scheduled recipe is absent until the cron flips it, and a draft never appears. Statuses and Publishing covers what each status makes visible.
Supports
Section titled “Supports”supports is the feature list a type opts into. Its type is readonly string[], and the code accepts any string you put in it.
Five values change what the product does.
titlerenders the title input on the editor.editorputs the entry on the block editor canvas. Omitsupportsaltogether and the canvas is on anyway, which keeps types registered before the list existed working. Supply asupportsarray withouteditorand the entry gets the plain form instead.excerptrenders the excerpt field.revisionsturns on snapshotting. Plumix captures nothing for a type that leaves it out.autosavegives a published entry a per-user pending draft, so editing a live recipe writes to that draft until you promote it.
versioning tunes the revision machinery, and Plumix reads it only when revisions is present. maxRevisions caps retained snapshots per entry at 25 by default, and autosaveIntervalSeconds sets the editor’s autosave cadence, 60 by default.
import { definePlugin } from "plumix/plugin";
export const recipes = definePlugin("recipes", { setup: (ctx) => { ctx.registerEntryType("recipe", { label: "Recipes", labels: { singular: "Recipe", plural: "Recipes" }, supports: ["title", "editor", "excerpt", "revisions", "autosave"], versioning: { maxRevisions: 50, autosaveIntervalSeconds: 30 }, }); },});Plugin-defined values work the same way. The comments plugin enables discussion on a type that declares comments, and reads the array directly. The pages plugin declares slug on page, which nothing in core branches on today, and that is exactly what an open list permits.
Visibility
Section titled “Visibility”isPublic is the master switch and defaults to true. Set it to false and the router compiles no routes for the type, while the permalink builder returns null for its entries. There is nothing public left to link to.
Narrower flags cascade from it, and you set one when you want a single surface to differ from the rest. Three of the four reach a surface today.
showUIsupplies the defaultshowInSidebarinherits, so turning it off takes the sidebar entry with it.showInSidebaris the flag the admin reads.falsedrops the type from the sidebar and from the dashboard’s quick-create tiles, which is how a plugin ships its own management page in place of the generic list.excludeFromSearchkeeps the type out of site search results while leaving its own URLs live.excludeFromGenericRpcresolves the same way, fromisPublic, and is stored on the registration. Nothing in core, the admin or any shipped plugin reads it today, and the manifest projection drops it, so setting it changes no behaviour.
Capabilities
Section titled “Capabilities”Registering a type mints eight capabilities named entry:<type>:<action>, each mapped to a minimum role on the ladder subscriber < contributor < author < editor < admin.
| Capability | Minimum role |
|---|---|
entry:recipe:read |
subscriber |
entry:recipe:create |
contributor |
entry:recipe:edit_own |
contributor |
entry:recipe:publish |
author |
entry:recipe:edit_any |
editor |
entry:recipe:delete |
editor |
entry:recipe:read_revisions |
editor |
entry:recipe:restore_revision |
editor |
capabilities raises or lowers the minimum role per action. capabilityType points a type’s capabilities at another name, so two types sharing capabilityType: "recipe" pool one permission set rather than minting two. The namespace is resolved once, at registration: the registered type always carries a capabilityType, the type’s own name unless it pools, and every capability check reads it from there, so a pooled type is gated by entry:recipe:* on every surface.
import { definePlugin } from "plumix/plugin";
export const recipes = definePlugin("recipes", { setup: (ctx) => { ctx.registerEntryType("recipe", { label: "Recipes", labels: { singular: "Recipe", plural: "Recipes" }, capabilities: { publish: "editor", create: "author" }, }); },});With that registration a contributor can no longer create a recipe, and publishing one takes an editor.
Related
Section titled “Related”Taxonomies attach to an entry type through the termTaxonomies option, and Taxonomies and Terms covers both sides of that pairing. Everything a recipe stores beyond title, slug, content and excerpt is a meta-box field. The routes described above are the router’s default output, and Permalinks and Slugs covers reshaping them. Themes match a template against the type name, which Template Hierarchy explains. The capability table is one half of the picture in Access & Identity; the other half is which role a principal holds.
Next steps
Section titled “Next steps”Read Taxonomies and Terms to classify recipes by cuisine and diet, then Statuses and Publishing for what happens between a draft and a live URL. When the model is settled, Meta Boxes is where you attach the Recipe details card that holds prep time, servings and ingredients.
Two built-in types are worth reading as worked registrations: Blog registers post with no archive and a posts prefix, and Pages registers a hierarchical page rooted at /.