blocks-dusted
v0.1.13
Published
Manifest-driven CLI for installing reusable Payload CMS blocks.
Readme
blocks-dusted
blocks-dusted installs reusable Payload CMS blocks into an existing Payload project.
The GitHub source repository is private. That does not prevent installation from npm after the package is published, including when the published npm package is public.
Project Objective
blocks-dusted is a reusable Payload CMS CLI.
The end goal is not simply a block installer.
It is a complete installation framework capable of progressively installing reusable Payload features into existing Payload projects.
Everything should be manifest-driven, repeatable, safe, versionable, and easy to extend.
Eventually a developer should be able to build an entire Payload project from reusable packages.
Example:
pnpm dlx blocks-dusted add DD-Hero
pnpm dlx blocks-dusted add DD-Contact
pnpm dlx blocks-dusted add DD-Header
pnpm dlx blocks-dusted add DD-Footerwithout manually copying files.
Current Status
Repository
https://github.com/Hadizainal/blocks-dustedCurrent visibility:
- Private
Current npm package:
blocks-dustedCurrent executable:
blocks-dustedPublished version:
0.1.1Verified working through:
pnpm dlx blocks-dustedThe CLI has been successfully tested from a completely separate Payload project.
Current CLI Features
Implemented
- manifest-driven registry
- block listing
- doctor command
- dry-run support
- block installation
- shared file installation
- dependency detection
- manual dependency reporting
- RenderBlocks patching
- Collection block registration
- collision protection
- shared file protection
- automatic backups
- installed block README generation
- package validation
- packed tarball validation
- clean-room installation tests
- npm distribution
Not implemented intentionally
- automatic dependency installation
- automatic Payload generators
- automatic migrations
- automatic builds
These should remain manual unless explicitly changed later.
Immediate Next Work
The existing blocks are not yet considered complete.
Many were converted from Designs Dusted and now require updating.
Examples include:
Component cleanup
Replace unnecessary wrappers.
Example:
<section>should become
<div>where appropriate.
Section IDs
Blocks should consistently expose configurable section IDs.
Many currently require updating.
URL fields
Numerous blocks still contain older URL field implementations.
They should be migrated to the latest shared URL configuration.
Button configuration
Older button implementations still exist.
These should be replaced with the current shared implementation.
Shared utilities
Many older helper implementations still exist.
Future block updates should migrate toward shared reusable utilities instead of duplicated logic.
Future Development
The project is moving beyond simple blocks.
The next stage introduces reusable project infrastructure.
Think in layers.
Layer 1
Reusable Blocks
Examples
- Hero
- Contact
- Pricing
- Carousel
- Testimonials
These install independently.
Layer 2
Reusable Core Components
Examples
- Button
- Rich Text
- Section wrapper
- URL utilities
- Shared fields
- Icon picker
- Media helpers
These are dependencies used by many blocks.
Layer 3
Reusable Globals
Examples
- Header
- Footer
- Logos
- Cookie Consent
- Newsletter Modal
Installation should:
- copy component
- register Payload global
- patch payload.config.ts
- preserve user changes
- create backups
Layer 4
Frontend Infrastructure
Examples
- RenderBlocks
- Layout
- Providers
- Navigation
- Animations
- Utilities
- Hooks
- Context Providers
These require safe project patching.
Layer 5
Project Features
Entire reusable systems.
Examples
- Quote System
- Newsletter
- Analytics
- Search
- Commerce
- Authentication
- OpenPanel
- Stripe
Each should install as a complete feature rather than individual files.
Installation Philosophy
Everything should remain manifest-driven.
Avoid project-specific logic.
Every installer should know:
- files
- shared files
- dependencies
- registrations
- patch operations
- manual follow-up
from the manifest.
Avoid hardcoding block-specific behaviour.
Safety Philosophy
The CLI must remain conservative.
Never overwrite existing work.
Always prefer reporting over guessing.
Continue protecting:
- destination collisions
- shared files
- backups
- dry-run
- manual dependency installation
- manual generators
Safety is more important than convenience.
Future Installer Capabilities
The installer will become progressively smarter.
Potential future installers include:
- blocks
- globals
- components
- providers
- utilities
- hooks
- layouts
- Payload plugins
- admin customisations
- frontend features
Each installer should follow the same architecture.
Codex Guidance
Codex should optimise for:
- reusable architecture
- manifest-driven behaviour
- minimal hardcoding
- safe source patching
- deterministic installs
- idempotent operations
- backward compatibility where appropriate
Avoid writing project-specific installers.
Everything should remain generic.
Definition of Success
Eventually this should become a package where installing an entire Designs Dusted ecosystem into a fresh Payload project is largely declarative.
For example:
pnpm dlx blocks-dusted add DD-Hero
pnpm dlx blocks-dusted add DD-Header
pnpm dlx blocks-dusted add DD-Footer
pnpm dlx blocks-dusted add DD-QuoteSystem
pnpm dlx blocks-dusted add DD-NewsletterEach command should:
- install files
- install shared resources
- safely patch the project
- report manual follow-up steps
- never destroy existing work
without requiring the installer to understand each feature individually.
Current Milestone
- Package published to npm.
- CLI verified from a clean Payload project.
- Manifest architecture established.
The next milestone is improving the quality and breadth of installable assets, not redesigning the CLI architecture. The architecture is now in place; future work should focus on expanding it while preserving its generic, manifest-driven design.
Main Workflow
Run commands from the root of your Payload project:
pnpm dlx blocks-dusted list
pnpm dlx blocks-dusted doctor
pnpm dlx blocks-dusted add DD-Hero --dry-run
pnpm dlx blocks-dusted add DD-Hero
pnpm dlx blocks-dusted add RichText
pnpm dlx blocks-dusted header add HeaderDD02 --dry-run
pnpm dlx blocks-dusted header add HeaderDD02Use blocks-dusted list first to see the exact available block names in the package. DD-Hero is an existing block in the current manifest registry.
The preferred install command after publication is:
pnpm dlx blocks-dusted add <TemplateName>Commands
blocks-dusted --help
blocks-dusted --version
blocks-dusted list
blocks-dusted doctor
blocks-dusted doctor --cwd C:\Projects\my-payload-site
blocks-dusted add DD-Hero --dry-run
blocks-dusted add DD-Hero --no-register
blocks-dusted add DD-Hero --cwd C:\Projects\my-payload-site
blocks-dusted add RichText --cwd C:\Projects\my-payload-site
blocks-dusted header add HeaderDD02 --dry-run
blocks-dusted header add HeaderDD02--cwd targets a Payload project directory without changing your shell directory. Relative --cwd values resolve from the directory where you launched the command.
Safety Behavior
--dry-run performs no writes.
--no-register copies block files and safe shared files but skips automatic collection and RenderBlocks registration.
Existing block destinations are protected from overwrite. Existing shared files are never overwritten.
Before supported source patches, the CLI creates backups such as .bak, .bak.1, and later numbered backups.
Dependencies are reported but not installed. Install the reported packages manually after reviewing the output.
Payload generators are never run automatically. Run these manually after reviewing changes:
pnpm payload generate:types
pnpm payload generate:importmapThe CLI does not run migrations or build the target project.
Header workflows
Headers are active Payload globals and layout wiring, not ordinary page blocks. Use the dedicated header workflow when you want to activate a reusable header implementation.
pnpm dlx blocks-dusted header add HeaderDD02 --dry-run
pnpm dlx blocks-dusted header add HeaderDD02The HeaderDD02 workflow backs up the active Header config, Nav renderer, and RowLabel before patching them. It installs the HeaderDD02 variant files, copies missing shared UI primitives, patches src/Header/Nav/index.tsx to render HeaderDD02, and patches src/Header/config.ts to expose HeaderDD02 fields while retaining legacy starter navItems as hidden data. Run Payload type and import-map generation after reviewing the changes.
Payload Block Standard
Purpose
This standard defines the required structure for Payload CMS frontend blocks. The objective is consistent markup, predictable targeting, manageable admin configuration, and block-specific CSS support.
Component Standards
Top-Level Wrapper
If the original block uses a top-level <section>, convert it to a <div>. RenderBlocks provides the section wrapper.
The top-level wrapper must include:
id={sectionId ?? undefined}
data-block-type="[component_name]"Section ID
Every converted block must support sectionId.
Destructure it from the generated Payload block props and apply it to the top-level wrapper:
id={sectionId ?? undefined}Custom CSS
Every converted block must support customCSS.
<>
{customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
<div>{/* block */}</div>
</>Do not remove or rewrite supplied custom CSS.
useInjectStyle Pattern
When original component SCSS must be converted, use the project useInjectStyle pattern:
import { useInjectStyle } from "@/hooks/useInjectStyle";
const bannerStyles = `
/* converted styles */
`;
useInjectStyle(bannerStyles, "banner-inline-styles");Required naming format:
[component_name]Styles
[component_name]-inline-stylesIf no converted CSS is required, the standard commented placeholders may remain.
Data Attributes
The top-level block must include:
data-block-type="[component_name]"Inner structural elements may use data-type when useful for identifying block structure. This is optional and depends on block complexity.
Example:
data-type="banner-style"RichText components should include:
data-content-type="[component_name]-richtext"Payload Config Standards
Section ID Field
{
name: 'sectionId',
type: 'text',
label: 'Section ID',
admin: {
description: 'Optional HTML ID used for anchor links and section targeting.'
}
}Custom CSS Field
Every converted block config must include:
{
name: 'customCSS',
type: 'code',
label: 'Custom CSS',
admin: {
language: 'css'
}
}Do not use a plain textarea for custom CSS.
Admin Viewport Management
Do not place customCSS directly in a long flat field list. Custom CSS can become large and create excessive vertical scrolling in the Payload admin viewport.
Place customCSS in either:
- A dedicated
Custom CSStab. - A collapsible field when tabs are unnecessary.
Prefer a dedicated Custom CSS tab when the block already benefits from grouped admin fields.
{
type: 'tabs',
tabs: [
{
label: 'Content',
fields: [
// block content fields
]
},
{
label: 'Custom CSS',
fields: [
{
name: 'customCSS',
type: 'code',
label: 'Custom CSS',
admin: {
language: 'css'
}
}
]
}
]
}Existing Fields and Behaviour
Preserve existing block fields, options, defaults, required states, frontend appearance, responsiveness, animations, pseudo-elements, specificity, and behaviour.
Do not invent new content fields beyond the standard sectionId and customCSS additions unless explicitly requested.
Do not manually alter generated Payload interfaces.
Canonical Component Pattern
import type { BannerBlock as BannerBlockProps } from "src/payload-types";
import { cn } from "@/utilities/ui";
import React from "react";
import RichText from "@/components/RichText";
// import { useInjectStyle } from '@/hooks/useInjectStyle' - uncomment if required
type Props = {
className?: string;
} & BannerBlockProps;
/* Inlined CSS (converted from the original SCSS to [component_name]Styles) format */
// const bannerStyles = `
// // add styles here if needed
// `
export const BannerBlock: React.FC<Props> = ({
className,
content,
style,
customCSS,
sectionId,
}) => {
// Inject styles once, regardless of how many instances are rendered.
// Keep format standardised: [component_name]Styles, [component_name]-inline-styles.
// useInjectStyle(bannerStyles, 'banner-inline-styles') - uncomment if required
return (
<>
{customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
<div
id={sectionId ?? undefined}
data-block-type="banner"
className={cn("mx-auto my-4 w-full", className)}
>
<div data-type="banner-style">
<RichText
data-content-type="banner-richtext"
data={content}
enableGutter={false}
enableProse={true}
/>
</div>
</div>
</>
);
};Execution Prompt
Review the target Payload CMS block component and its block config, then update both to comply with
Payload Block Standard.md.Inspect the existing component and config before editing. Preserve the block's current appearance, responsive behaviour, animations, pseudo-elements, selectors, specificity, field options, defaults, required states, and functionality.
Apply the standard carefully:
- Convert a top-level
<section>to<div>becauseRenderBlocksprovides the section wrapper.- Add
sectionIdsupport and applyid={sectionId ?? undefined}to the top-level block wrapper.- Add
data-block-type="[component_name]"to the top-level block wrapper.- Add useful
data-typeattributes only where inner structural identification is beneficial.- Add
data-content-type="[component_name]-richtext"to RichText components.- Add
customCSSsupport and render it with<style dangerouslySetInnerHTML={{ __html: customCSS }} />.- If component SCSS exists, convert it to the
useInjectStylepattern using[component_name]Stylesand[component_name]-inline-styles.- Add matching
sectionIdandcustomCSSfields to the Payload block config.customCSSmust usetype: 'code'withadmin.language: 'css'.- Place
customCSSin a dedicated tab or collapsible field so long CSS does not create excessive admin viewport scrolling.- Preserve all existing block-specific fields and behaviour.
- Do not invent fields, redesign the component, refactor unrelated code, or change behaviour outside this standard.
Update only the files required for this block. Review the final diff against the standard before completing the task.
Payload Rich Text Dusted Standard
Purpose - RichText Dusted Standard
This document defines the standard folder structure and implementation rules for a reusable frontend RichText component in a Payload CMS project.
This is a standard, not a requirement that every Payload project must contain every optional feature described below. Always inspect the target project before making changes. Preserve project-specific requirements and add only the parts that the project needs.
The objective is to keep RichText rendering:
- predictable;
- reusable;
- typed;
- easy to extend;
- separated by responsibility;
- compatible with the target project's Payload schema;
- free from unrelated project-specific assumptions.
Standard Folder Structure
src/
+-- components/
+-- RichText/
+-- index.tsx
+-- converter/
+-- index.tsx
+-- internalLinks.tsx
+-- textConverter.tsx
+-- componentConverter/
+-- blocks.tsx
+-- types.tsBase and Optional Files
Base RichText structure
These files form the base structure:
src/components/RichText/index.tsx
src/components/RichText/converter/index.tsx
src/components/RichText/converter/internalLinks.tsx
src/components/RichText/converter/textConverter.tsxOptional embedded-block extension
Add this folder only when the target project permits Payload blocks to be embedded inside RichText:
src/components/RichText/converter/componentConverter/blocks.tsx
src/components/RichText/converter/componentConverter/types.tsDo not create placeholder block mappings when RichText does not support embedded blocks.
Responsibility of Each File
src/components/RichText/index.tsx
This is the public RichText component and the only normal import entry point for other frontend components.
It must:
- accept the RichText value produced by the target Payload field;
- handle an empty or missing value safely;
- call the RichText renderer with the project's converter configuration;
- accept presentation props only when the project needs them;
- preserve the caller's
classNameinstead of replacing it; - remain focused on rendering the complete RichText value.
Other files should normally import RichText from:
import { RichText } from "@/components/RichText";Use the actual export style already established by the target project. Do not change named exports to default exports, or default exports to named exports, unless the task explicitly requires it.
Do not place individual Lexical node conversion logic directly in this file.
src/components/RichText/converter/index.tsx
This is the converter entry point and orchestration layer.
It must:
- assemble the converters used by RichText;
- connect the text converter;
- connect the internal-link converter;
- connect embedded-block converters only when supported;
- preserve required default converters from the Payload Lexical renderer;
- keep converter registration in a clear and deterministic order;
- export the converter value or converter factory required by
RichText/index.tsx.
Do not turn this file into one large converter containing all text, link, relationship, upload and block rendering logic.
src/components/RichText/converter/internalLinks.tsx
This file owns the rendering and URL resolution of Payload internal-document links.
It must:
- inspect the linked relationship safely;
- determine the linked document type using the actual project schema;
- resolve the URL from the linked document's real fields;
- support only collections or globals that exist in the target project;
- return a safe fallback when the relationship is missing, unresolved or unsupported;
- preserve link children and relevant attributes;
- use the project's existing link or URL utilities when available.
It must not:
- assume every project uses the same collections;
- assume every linked document has a
slug; - invent routes;
- hardcode a route copied from another project without verifying it;
- treat an unresolved relationship as a valid URL.
Examples of collection names, route prefixes and slug rules are project variations. They do not belong in the reusable standard unless the target project actually uses them.
src/components/RichText/converter/textConverter.tsx
This file owns the conversion and rendering of Lexical text nodes.
It must:
- preserve the text content;
- preserve every supported text format used by the target editor;
- combine compatible formats when more than one format is applied;
- render unsupported or unformatted text safely;
- use valid React elements;
- keep formatting behaviour independent from block rendering.
Possible text formats may include:
- bold;
- italic;
- underline;
- strikethrough;
- code;
- subscript;
- superscript.
The actual supported formats must be taken from the target project's editor configuration and existing implementation. Do not add formatting merely because it appears in this list.
src/components/RichText/converter/componentConverter/blocks.tsx
This file owns the mapping between RichText embedded-block nodes and their React components.
Create it only if blocks can be embedded in RichText.
It must:
- map each supported Payload block slug to the correct frontend component;
- pass the block data using the prop shape expected by that component;
- return a safe result for an unknown or malformed block;
- keep project-specific block imports in this file;
- make the list of supported embedded blocks easy to review;
- preserve type safety where the generated Payload types allow it.
It must not:
- import every block in the repository automatically;
- assume every page block is suitable inside RichText;
- reuse the main page
RenderBlockscomponent unless its contract is verified as compatible; - silently invent props for a block component;
- add block mappings that do not exist in the target Payload schema.
src/components/RichText/converter/componentConverter/types.ts
This file contains types used specifically by embedded component or block conversion.
It must:
- describe the actual embedded-block node or converter contract;
- reuse generated Payload types where practical;
- keep local helper types narrow;
- avoid duplicating the entire generated block schema;
- contain types only, unless a small type guard is genuinely required.
Do not manually edit src/payload-types.ts to make these types compile. Payload-generated types must be regenerated using the project's existing Payload type-generation command.
Required Implementation Process
An AI or developer implementing this standard must complete these steps in order.
1. Inspect the target project
Before writing code, locate and read:
- the Payload RichText field configuration;
- the Lexical editor configuration;
- the generated RichText data type;
- existing link and URL utilities;
- collections or globals allowed as internal links;
- embedded block definitions, if any;
- existing frontend block components;
- the project's import alias configuration;
- the project's package versions and installed RichText packages.
Do not start by copying a RichText component from another repository.
2. Classify the project variation
Determine whether the project needs:
- base RichText rendering only;
- internal links;
- uploads or media rendering;
- custom text formats;
- relationships;
- embedded blocks;
- project-specific converters.
Only install the necessary variation.
3. Preserve existing behaviour
If RichText already exists, preserve:
- supported node types;
- internal-link routes;
- external-link behaviour;
- classes and styling hooks;
- media behaviour;
- embedded blocks;
- generated types;
- public component props;
- current import paths.
Refactoring the folder structure does not authorise removing behaviour.
4. Separate responsibilities
Move logic to the file that owns it:
| Concern | File |
| -------------------------------- | ------------------------------- |
| Public RichText component | RichText/index.tsx |
| Converter assembly | converter/index.tsx |
| Internal-document URL resolution | converter/internalLinks.tsx |
| Text-node formatting | converter/textConverter.tsx |
| Embedded-block mapping | componentConverter/blocks.tsx |
| Embedded-block converter types | componentConverter/types.ts |
Do not create additional files without a real project requirement.
5. Validate
Run the target project's existing:
- formatter;
- linter;
- TypeScript check;
- relevant tests;
- production build, when within the task scope.
If the Payload schema or generated types changed, run the project's existing Payload type-generation command before the final type check.
Do not report completion when imports, types or converter mappings are unverified.
Import and Dependency Rules
- Use packages already installed by the target project.
- Verify exact APIs against the installed package versions.
- Follow the project's existing alias and import conventions.
- Prefer generated Payload types over broad
anytypes. - Use
unknownplus narrowing when incoming converter data cannot be trusted. - Avoid circular imports between RichText and block components.
- Do not import server-only modules into client components.
- Add
'use client'only when the component genuinely uses client-only React features. - Do not add a package solely because another Dusted project uses it.
- Do not change package versions unless explicitly required.
Data and Safety Rules
- Treat RichText data as nullable unless the project type proves otherwise.
- Handle missing relationships and deleted linked documents safely.
- Do not render
undefined, malformed URLs or invented fallbacks as working links. - Do not use
dangerouslySetInnerHTMLmerely to bypass converter work. - Do not discard unknown content without making the fallback behaviour deliberate.
- Do not log the full RichText document in production.
- Do not place private Payload document data into frontend URLs or attributes.
Styling Rules
The RichText renderer provides structure and semantic output. Styling remains project-specific.
- Preserve existing classes and data attributes.
- Allow a caller-provided
classNamewhen the current component contract supports it. - Do not copy typography styles from another project as part of this standard.
- Do not hardcode a theme.
- Do not place an entire project's prose CSS inside the converter.
- Keep semantic elements such as headings, paragraphs, lists, links and code output intact.
For RichText used inside a reusable Payload block, follow the block standard for any required content hook, including:
data-content-type="[component_name]-richtext"Replace [component_name] with the actual component identifier. Do not use the placeholder literally.
Extension Rules
The structure may be extended when the target project genuinely requires another distinct converter concern.
Examples may include:
converter/
+-- uploads.tsx
+-- relationships.tsx
+-- headings.tsxBefore adding a file, confirm that:
- the relevant node exists in the target editor configuration;
- the logic is substantial enough to deserve separation;
- the responsibility does not already belong to an existing file;
- the new file does not introduce a project-specific feature into the reusable base unnecessarily.
Variations must be documented rather than forced into all projects.
Prohibited Shortcuts
Do not:
- place all converter logic in
RichText/index.tsx; - copy imports, collection slugs or routes from another project without checking;
- create empty files solely to match the tree;
- add embedded-block support to a project that does not use it;
- map unsupported blocks;
- edit generated Payload types manually;
- replace strict types with
anymerely to silence errors; - remove default Lexical converters accidentally;
- assume package APIs are identical across Payload versions;
- rename the public RichText import path without updating every consumer;
- claim the implementation is complete without running the available validation commands.
Completion Checklist
Before marking RichText work complete, confirm every applicable item:
- [ ] The target Payload editor configuration was inspected.
- [ ] The target project's existing RichText behaviour was inspected.
- [ ] The public component is located at
src/components/RichText/index.tsx. - [ ] Converter assembly is located at
converter/index.tsx. - [ ] Internal-link logic is isolated in
internalLinks.tsx. - [ ] Text formatting is isolated in
textConverter.tsx. - [ ]
componentConverter/exists only if embedded blocks are supported. - [ ] Every embedded block mapping refers to a real schema block and real component.
- [ ] Internal routes are derived from verified project rules.
- [ ] Empty, missing and unresolved values are handled safely.
- [ ] Existing styles, classes and attributes are preserved.
- [ ] No generated Payload type file was manually edited.
- [ ] No unnecessary dependency was added.
- [ ] Formatting passed.
- [ ] Linting passed.
- [ ] Type checking passed.
- [ ] Relevant tests passed.
- [ ] The production build passed when it was part of the requested scope.
Definition of Done
The RichText standard is correctly implemented when:
- other components have one clear RichText import entry point;
- converter responsibilities are separated into the standard files;
- the implementation matches the target Payload schema and installed package versions;
- project-specific variations are included only when needed;
- existing behaviour has not been lost;
- malformed or missing data fails safely;
- the target project's validation commands pass.
Publishing Identities
- GitHub repository:
https://github.com/Hadizainal/blocks-dusted.git - npm package:
blocks-dusted - CLI executable:
blocks-dusted
The repository can remain private while the npm package is published separately.
DesignsDustedv4 Sandbox Sync
Last updated: 2026-08-22.
The current local CLI version in package.json is 0.1.10.
blocks-dusted/templates/blocks is the installable template source for productized DD/DDHG blocks. DesignsDustedv4 keeps matching sandbox runtime copies for its product catalogue previews.
Matching runtime source:
D:\CodeDusted\DesignsDustedv4\src\sandbox\products\DDBlockProductSandboxes\blocksInstallable template source:
D:\CodeDusted\Dusted-Payload-Blocks\blocks-dusted\templates\blocksCurrent synced state:
- DesignsDustedv4 sandbox product block folders: 68
- blocks-dusted template block folders: 68
- shared sandbox/template files compared: 179
- shared file mismatches after sync: 0
- expected extra template files: 68
config.tsfiles - extra files outside
config.ts: 0
config.ts is installer/admin schema metadata and intentionally exists only in blocks-dusted. Do not copy those files into the DesignsDustedv4 sandbox unless the app runtime explicitly needs them. Do not delete them from blocks-dusted.
When changing a productized block, update both the DesignsDustedv4 sandbox runtime file and the matching blocks-dusted/templates/blocks runtime file. Keep fixture/demo copy in DesignsDustedv4 public-safe: no local paths, repository names, private data, registry wiring copy, iframe copy, or sandbox-render implementation copy.
