10x-media plugins
Admin Wiki

Authoring

The guide collection, the wiki editor, and the blocks available while writing.

The guide collection

wiki-pages is an ordinary Payload collection with drafts enabled. Its form splits across two unnamed tabs.

Guide

FieldTypeNotes
titletextRequired. Localized when your config declares localization
summarytextareaMax 400 characters (SUMMARY_MAX_LENGTH). Shown in hover cards, drawers, and lists. Localized
contentrichTextThe wiki editor. Localized

Targets

Four lists, one per target kind. Collections, Globals, and Blocks are pickers over what the plugin covers. Fields is a list grouped by owner, filled from a picker that renders your real fields or from a path typed by hand. See Targeting.

Sidebar

FieldTypeNotes
slugtextUnique and indexed. Generated from the title; the identifier in wiki URLs
featuredcheckboxLeads the wiki index and the list band
featuredOrdernumberLower first among featured guides. Shown only when featured is on

The tabs are unnamed, so they group the form without adding a level to the stored document. targetCollections and friends stay at the top level.

Only published guides reach surfaces, the targets map, and the wiki view. A draft is visible to its author in the collection and nowhere else.

The editor

Guides use their own lexical editor with an explicit feature list. It does not inherit the consuming project's richText configuration, in either direction: a project's custom nodes and link customizations never appear in guide content, and changing the project editor never changes what guides can contain.

Enabled:

  • Paragraphs, headings h2 to h4, bold, italic, underline, strikethrough, inline code
  • Ordered and unordered lists, blockquotes, horizontal rules, alignment, indentation
  • Links, external URLs only; guide-to-guide links are a feature of their own beside them
  • Uploads, scoped to the wiki-media collection
  • Fixed and inline toolbars

Headings start at h2; the guide title is the h1 on the page and in the drawer.

A guide link wraps text the way a link does, rather than sitting in the sentence as a chip: select the words, press the guide-link button in either toolbar, and pick the target from the guides list. The words stay yours to edit afterwards.

With the cursor inside a guide link, a small panel names the guide it points at and offers the two things there are to do: point it at a different guide, or take the link off and keep the text. The toolbar button is lit while the cursor is in a link, and pressing it there removes the link as well.

The link stores the target guide's id and nothing else. A reader clicking one opens that guide in a drawer stacked on the current one, so following a reference never leaves the page; a link whose target was deleted degrades to dimmed plain text, and the sentence still reads.

The words are content, not a rendering of the target's title. Renaming a guide therefore leaves the links pointing at it saying what their authors wrote, which is usually what a sentence needs, and occasionally something to go back and reword.

Built-in blocks

Callout. Four variants: info, tip, warning, danger. The body is a nested editor: inline formatting, links, lists, images, video, and the blocks you marked nestable.

Video embed. Only when video is enabled. Takes a YouTube or Vimeo watch, share, or short URL and validates it at authoring time; the reader gets a privacy-friendly embed (youtube-nocookie.com, Vimeo with dnt=1).

Video

Video is off by default. Enabling it makes the wiki-media collection accept video/* mimetypes alongside images, adds the video embed block to the editor, and lets uploaded videos be dropped into content as a video node.

adminWiki({ video: true })

Uploaded video plays through a plain HTML5 player. To use your own:

adminWiki({
  video: { playerComponent: '/components/WikiPlayer#WikiPlayer' },
})

The component is a client component resolved from the import map, and it receives the wiki-media document as media. The plugin registers it under config.admin.dependencies, so payload generate:importmap picks it up without you referencing it anywhere else. A path missing from the import map logs a warning and falls back to the default player.

Your own blocks

Add project blocks to the wiki editor by pairing each block with the client component that renders it in a published guide:

import { tipBlock } from './blocks/tipBlock'

adminWiki({
  editor: {
    blocks: [{ block: tipBlock, component: '/components/TipBlock#TipBlock' }],
  },
})

The renderer receives the block node's field values as fields, and is registered as an admin dependency so importmap generation finds it. A renderer declared but missing from the import map renders a visible placeholder and logs a warning, rather than dropping the block silently.

nestable: true also offers the block inside a nested editor, a callout body among them. Blocks are not nestable by default, inline blocks are:

blocks: [{ block: tipBlock, component: '/components/TipBlock#TipBlock', nestable: true }],
inlineBlocks: [{ block: heroChip, component: '/components/HeroChip#HeroChip', nestable: false }],

Inline blocks

For a block that sits inside a sentence, a chip or a status badge, use inlineBlocks:

payload.config.ts
import { statusChipBlock } from './blocks/statusChip'

adminWiki({
  editor: {
    inlineBlocks: [{ block: statusChipBlock, component: '/components/StatusChip#StatusChip' }],
  },
})

Its renderer has to return inline-level JSX.

Your own features

A block covers content that sits in the document as a unit. For everything else a lexical feature can do (a custom node, a toolbar item, a keyboard shortcut, a markdown transform), pass the feature itself:

import { MyFeature } from './lexical/MyFeature'

adminWiki({
  editor: {
    features: [MyFeature({ /* ... */ })],
  },
})

An array is appended to the plugin's own features. A feature's client half is resolved through the import map like any other, so nothing has to be declared twice.

The function form is handed the plugin's features as defaultFeatures and returns the whole list, which is the way to reorder or drop one. It runs once per editor, nested telling a nested body apart from the guide body:

adminWiki({
  editor: {
    features: ({ defaultFeatures }) => [
      ...defaultFeatures.filter((feature) => feature.key !== 'align'),
      MyFeature(),
    ],
  },
})

The list you return is the editor. Dropping wikiGuideLink takes guide links with it, and dropping blocks takes the callout, the video embed, and your own blocks. The plugin does not put anything back.

Features reach the seed as well: seedWiki builds the same editor the collection uses, so markdown converts through your features too.

Your own converters

Reach for converters when a feature contributes a node type of its own; a block already carries its renderer as component.

Point the plugin at a client module exporting a JSXConvertersFunction, the same shape as the converters prop on Payload's RichText:

wikiConverters.tsx
'use client'
import type { JSXConvertersFunction } from '@payloadcms/richtext-lexical/react'

export const wikiConverters: JSXConvertersFunction = ({ defaultConverters }) => ({
  ...defaultConverters,
  myNode: ({ node, nodesToJSX }) => <MyNode>{nodesToJSX({ nodes: node.children })}</MyNode>,
})
payload.config.ts
adminWiki({
  editor: { converters: '/components/wikiConverters#wikiConverters' },
})

It must be a client module.

blocks and inlineBlocks are nested maps, replaced whole. Returning { ...defaultConverters, blocks: { mine } } drops the callout, the video embed and every consumer block; spread defaultConverters.blocks too.

Converters apply wherever the plugin renders a guide: the wiki view, the surface drawers, and a callout body.

The editor inside your own block

A block with a richText field of its own can run the wiki editor there. nested: true drops the callout and headings, then the document furniture a framed body has no use for: blockquotes, horizontal rules, indentation and the fixed toolbar. Lists, images, video and your nestable blocks stay.

Everything else comes from the arguments, so hand it the same object you hand adminWiki as editor, plus the collection slugs, which live beside editor rather than inside it:

blocks/accordion.ts
import { wikiFeatures } from '@10x-media/admin-wiki'
import { lexicalEditor } from '@payloadcms/richtext-lexical'

import { wikiEditorOptions } from '../wikiEditorOptions'

export const accordionBlock = {
  slug: 'accordion',
  fields: [
    { name: 'title', type: 'text' },
    {
      name: 'body',
      type: 'richText',
      editor: lexicalEditor({
        features: () =>
          wikiFeatures({
            ...wikiEditorOptions,
            mediaSlug: 'wiki-media',
            nested: true,
            pagesSlug: 'wiki-pages',
          }),
      }),
    },
  ],
}

A block whose own body runs wikiFeatures must not also appear in blocks with nestable: true. That makes the block reachable from inside itself, and Payload follows the two into each other while sanitizing the config, so the app never finishes booting.

mediaSlug and pagesSlug are required, because an editor pointing at collections you renamed through slugs would look exactly like one you configured. blocks, inlineBlocks and video still default to empty, so an editor missing yours is silent.

To render the field, take converters and nodesToJSX beside fields:

components/Accordion.tsx
'use client'

export const Accordion = ({ converters, fields, nodesToJSX }) => (
  <details>
    <summary>{fields.title as string}</summary>
    {nodesToJSX?.({ converters, nodes: fields.body.root.children })}
  </details>
)

Wiki media

wiki-media is the wiki's own upload collection, used only by wiki content: images always, video mimetypes when video is enabled, plus a localized alt field. Guide screenshots therefore stay out of the media picker your editors use for the site itself.

Both collections take an admin.hidden passthrough:

adminWiki({
  hidden: { media: true, pages: ({ user }) => !user?.roles?.includes('editor') },
})

Next

Seeding to write guides from markdown in code, or The wiki view for how a finished guide reads.

On this page