Macros
Last modified by Manuel Leduc on 2026/07/20 10:51
Developer
See the new documentation.
Declaring a macro
See the new documentation.
AST-based rendering
Macros don't render directly to HTML or React JSX ; instead they render to an editor-agnostic, framework-agnostic AST type that's converted by Cristal to a format that can be understood by the editor, or directly to HTML in view mode.
/**
* Block returned by a macro
*
* @since 0.23
* @beta
*/
type MacroBlock =
| {
type: "paragraph";
styles: MacroBlockStyles;
content: MacroInlineContent[];
}
| {
type: "heading";
level: 1 | 2 | 3 | 4 | 5 | 6;
content: MacroInlineContent[];
styles: MacroBlockStyles;
}
| {
type: "list";
numbered?: boolean;
items: MacroListItem[];
styles: MacroBlockStyles;
}
| { type: "quote"; content: MacroBlock[]; styles: MacroBlockStyles }
| { type: "code"; language?: string; content: string }
| {
type: "table";
columns: MacroTableColumn[];
rows: MacroTableCell[][];
styles: MacroBlockStyles;
}
| ({
type: "image";
} & MacroImage)
| {
type: "macroBlock";
name: string;
params: Record<string, boolean | number | string>;
}
| { type: "rawHtml"; html: string }
| { type: "macroBlockEditableArea" };
/**
* Styles for a `MacroBlock`
*
* @since 0.23
* @beta
*/
type MacroBlockStyles = {
cssClasses?: string[];
// TODO: theme's CSS variables
textColor?: string;
// TODO: theme's CSS variables
backgroundColor?: string;
textAlignment?: MacroAlignment;
};
/**
* Alignment for an item
*
* @since 0.23
* @beta
*/
type MacroAlignment = "left" | "center" | "right" | "justify";
/**
* Item that's part of a `MacroBlock` list
*
* @since 0.23
* @beta
*/
type MacroListItem = {
checked?: boolean;
content: MacroInlineContent[];
styles: MacroBlockStyles;
};
/**
* Image returned by a macro
*
* @since 0.23
* @beta
*/
type MacroImage = {
target: MacroLinkTarget;
alt?: string;
widthPx?: number;
heightPx?: number;
};
/**
* Link returned by a macro
*
* @since 0.23
* @beta
*/
type MacroLink = {
target: MacroLinkTarget;
content: Exclude<MacroInlineContent, { type: "link" }>[];
};
/**
* Column that's part of a `MacroBlock` table
*
* @since 0.23
* @beta
*/
type MacroTableColumn = {
headerCell?: {
content: MacroInlineContent[];
styles: MacroBlockStyles;
};
widthPx?: number;
};
/**
* Cell that's part of a `MacroBlock` table
*
* @since 0.23
* @beta
*/
type MacroTableCell = {
content: MacroInlineContent[];
styles: MacroBlockStyles;
rowSpan?: number;
colSpan?: number;
};
/**
* Inline content returned by a macro
*
* @since 0.23
* @beta
*/
type MacroInlineContent =
| ({ type: "text" } & MacroText)
| ({ type: "link" } & MacroLink)
| { type: "rawHtml"; html: string }
| {
type: "inlineMacro";
name: string;
params: Record<string, boolean | number | string>;
}
| {
type: "macroBlockEditableArea";
styles: MacroBlockStyles;
};
/**
* Text that's part of a `MacroInlineContent`
*
* @since 0.23
* @beta
*/
type MacroText = {
content: string;
styles: MacroTextStyles;
};
/**
* Styles for a `MacroText`
*
* @since 0.23
* @beta
*/
type MacroTextStyles = {
bold?: boolean;
italic?: boolean;
strikethrough?: boolean;
underline?: boolean;
code?: boolean;
textColor?: string;
backgroundColor?: string;
};
/**
* Link target returned by a macro
*
* @since 0.23
* @beta
*/
type MacroLinkTarget =
| {
type: "internal";
rawReference: string;
}
| { type: "external"; url: string };Example
import { injectable } from "inversify";
import type {
BlockMacro,
GetConcreteMacroParametersType,
MacroBlock,
MacroInfos,
} from "@xwiki/cristal-macros-api";
// Define the macro parameters
// These must adhere to the `Record<string, MacroParameterType>` constraint
const macroParams = {
name: { type: "string" },
uppercase: { type: "boolean" },
} as const;
// Extract the params' type for easier access
type MacroParams = typeof macroParams;
// Define an injectable Inversify class
@injectable()
export class SayMyNameMacro implements BlockMacro<MacroParams> {
// Setup the macro's informations
readonly infos: MacroInfos<MacroParams> = {
// The macro's ID (must be letters, digits and underscores only)
id: "sayMyName",
// The name to display in the editor's menus
name: "Say My Name",
// The description to display in the editor
description: "A macro to say your name",
// The list of all parameters along with their types
params: macroParams,
// The description of all parameters for the editor
paramsDescription: {
name: "Your name",
uppercase: "Show the name in uppercase characters",
},
// A set of default parameters to enable inserting the macro into the editor
// If `false` is provided, the macro will not be user-insertable
defaultParameters: {
name: "",
uppercase: false,
},
};
// Indicate the macro renders as a block (it could also render as an inline content)
renderAs = "block" as const;
// The rendering function...
// ...which returns a universal macro AST type
render({
name,
uppercase,
}: GetConcreteMacroParametersType<MacroParams>): MacroBlock[] {
return [
{
type: "paragraph",
styles: {},
content: [
{
type: "text",
styles: {},
content: "Hello, ",
},
{
type: "text",
styles: { bold: true },
content: uppercase ? name.toLocaleUpperCase() : name,
},
{
type: "text",
styles: {},
content: "!",
},
],
},
];
}
}

This project is being financed by the French State as part of the France 2030 program
Ce projet est financé par l’État Français dans le cadre de France 2030