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: "!",
          },
        ],
      },
    ];
  }
}
France 2030 Logo

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

  • Powered by XWiki 17.10.9-node2. Hosted and managed by XWiki SAS

Get Connected