Skip to content
Menu
Search
Browse documentation & this page

Developer Guide

Adding tree actions

Put an action of your own on the right-click menu of a page, block, media file, folder or category.

The right-click menu on every tree in the editor is extensible. An action you write appears on the menu beside Cut, Copy and Paste, on the kinds of item you choose, and is handed the item the author clicked.

An action is JavaScript: it runs in the editor, in the author's browser. Anything it needs from the server it asks for from an endpoint of yours, which checks the author's rights itself.

Step 1: write the script

Add a script to your site's wwwroot. This example is wwwroot/js/cms-tree-actions.js.

window.cmsExtensions.addTreeAction({
    target: 'page',
    label: 'Send for Translation',
    visible: function (node, context) { return context.holds('Edit'); },
    action: function (node, context) {
        fetch('/api/translations/send', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ itemId: node.itemId, culture: context.culture })
        }).then(function () { context.reload(); });
    }
});
Property Required What it does
target yes Which items carry the action. One of the targets below, or an array of them.
label yes What the menu says. A string, or a function (node, context) returning one.
action yes Runs when the author chooses it. Receives (node, context).
icon no One character shown before the label.
visible no A function (node, context). Return false to leave the action off this item's menu.
enabled no A function (node, context). Return false to show the action greyed out.
order no Position among extension actions; lower is higher. Ties keep the order they were added in.
danger no true draws the row in the destructive colour.

Targets

Target The item
page A page in the Pages tree
block A block or a form
blockFolder A folder in the Blocks or Forms tree
media A media file
mediaFolder A media folder
category A category

The node

node is the item as the tree holds it. Every target has an id and a name: node.itemId and node.name (a category has node.categoryId). A page also carries urlSegment, contentTypeId and origin; a media file carries category (Image, Document…).

The context

Member What it is
context.target The target this menu was opened for.
context.culture The language the author is working in.
context.releaseSlug The release the author is working in, or null for Live.
context.scope For blocks: shared, local or forms.
context.holds(right) Whether the author holds Read, Edit, Create, Delete or Publish on this item.
context.reload() Redraws the tree. Call it after an action that changed something.

holds is for leaving an action off a menu where it would only be refused. It is not the check: your endpoint decides.

Step 2: load it into the editor

Name the script in Program.cs:

builder.Services.AddCendia(options =>
{
    builder.Configuration.GetSection("Cendia").Bind(options);
    options.EditorScripts.Add("/js/cms-tree-actions.js");
});

The editor loads it after its own scripts and before the page starts. The address carries a hash of the file's content, so an edit is picked up without clearing a cache. The public site never requests it.

Step 3: run it

Restart the site, open the editor and right-click an item. Your actions are in a group of their own, above Move to Trash where the menu has one and at the end where it does not.

Rules to know

  • One item at a time. With several blocks or files selected, the menu is the batch menu and extension actions are left off it.
  • A label is text. A page name with < in it is shown as written, not as markup.
  • An action that throws is reported in the browser console and loses only its own row. The rest of the menu still works.
  • The menu costs nothing until it is opened. Registering an action makes no request. visible and enabled run when an author right-clicks, so keep them to what the node and the context already say; do not call the server from them.
  • Use window.cmsDialog for a confirmation or a message (confirm, alert, prompt), so your action asks the way the rest of the CMS does.

From a package

A NuGet package does the same from its own AddXxx() call, with the script in the package's wwwroot:

public static IServiceCollection AddAcmeTranslations(this IServiceCollection services) =>
    services.AddCendiaExtension<AcmeTranslationsMarker>(extension => extension
        .AddEditorScript("js/translations.js")
        .AddEditorStylesheet("css/translations.css"));

A relative path is served from /_content/<assembly name>/, which is where a Razor Class Library's wwwroot lands. If the package id differs from the assembly name, set extension.AssetBasePath. A package's files load ahead of the site's own.