Live Data Extension Points

Last modified by Eleni Cojocariu on 2026/09/11 15:51

Reference

Live Data extends on both sides of its JSON configuration: on the server by contributing a Component, in the browser by registering a component on the widget. A configuration naming the new hint is enough to reach it.

Extension pointKindLooked up underContributes
LiveDataSourceJava ComponentThe value of the source Macro parameterA new origin for the entries and the property descriptors
LiveTableNewRowNamingStrategyJava ComponentThe value of the newRowNamingStrategy source parameterA way of naming the pages created for new entries by the Live Table source
registerPanelJavaScriptN/AA new panel in the Live Data menu
componentStore.registerJavaScriptA kind and a nameA new displayer, filter or layout

Adding a Source

A source is what puts a set of entries within reach of page authors: register a Component under a hint, and that hint becomes a value they can pass as the source Macro parameter. The Component itself is small, and hands the real work to two stores.

@Role
public interface LiveDataSource
{
    LiveDataEntryStore getEntries();

    LiveDataPropertyDescriptorStore getProperties();
}

Reading and Writing the Entries

LiveDataEntryStore is where the data comes from. Only the two read methods have to be written; every other method has a default implementation, and the two that make the Live Data writable throw UnsupportedOperationException by default. A store that keeps those defaults gives a Live Data the reader can browse but not change. Each method also throws LiveDataException.

MethodReturnsWrite it
get(LiveDataQuery)The entries matching the query, and how many there are in totalAlways
get(Object entryId)One entry, by its idAlways
get(Object entryId, String property)One value of one entryOnly to avoid fetching the whole entry, which is what the default does
save(Map)The id of the entry that was storedTo let the reader create entries, and to let them edit values in place, which the default update goes through
update(Object, String, Object)The value the property had beforeOnly to replace the default, which reads the entry, sets the property and saves it
remove(Object entryId)The entry that was removedTo let the reader delete entries

Declaring the Properties

LiveDataPropertyDescriptorStore decides what the widget knows about each property: its type, whether it can be sorted, filtered or edited, and which displayer and filter it uses. What this store returns becomes the meta.propertyDescriptors of the Live Data Configuration, so it is what a reader sees in the Properties, Sort and Filter panels.

MethodReturnsWrite it
get()Every property the entries may haveAlways
get(String propertyId)One descriptorOnly to replace the default, which picks it out of the list above
save(LiveDataPropertyDescriptor)Whether the descriptor was storedTo let properties be redefined at runtime
remove(String propertyId)The descriptor that was removedTo let properties be dropped at runtime

Naming the Pages Created for New Entries

XWiki 18.7.0+

For the live table source, new row creation requires a page naming strategy. For that, there is a specific interface:

/**
 * Strategy to generate the document reference of a new livetable entry.
 *
 * @since 18.7.0RC1
 */
@Role
@Unstable
public interface LiveTableNewRowNamingStrategy
{
    /**
     * Generates a document reference for a new livetable entry.
     *
     * @param parameters the livedata source parameters
     * @return the generated document reference
     * @throws LiveDataException if the reference cannot be generated
     * @throws XWikiException if there is a wiki-level error
     */
    DocumentReference generate(Map<String, Object> parameters) throws LiveDataException, XWikiException;

    /**
     * Checks if the current user is allowed to create a new entry based on the provided source parameters.
     *
     * @param parameters the live data source parameters
     * @return whether the current user is allowed to create a new entry with this strategy
     */
    boolean isCreationAllowed(Map<String, Object> parameters);
}

New strategies can be added by creating new named components implementing this interface, and naming them in the newRowNamingStrategy source parameter of a Live Data.

Adding a Panel

A panel is registered on an instance with registerPanel, from a listener of the xwiki:livedata:instanceCreated event. The reader then toggles it from the Live Data menu, like the built-in panels. This registers a "Hello World" panel:

document.addEventListener('xwiki:livedata:instanceCreated', function(e) {
  const panel = {
    id: 'myExtension',
    name: 'My Extension',
    title: 'Hello World',
    icon: 'camera',
    container: document.createElement('div'),
    component: 'LiveDataAdvancedPanelExtension',
    order: 4000
  };

  panel.container.textContent = 'Hello World!';

  e.detail.livedata.registerPanel(panel);
});
PropertyWhat it holds
idA name unique among all the panels of the instance
nameThe text displayed in the Live Data menu
titleThe text displayed in the title of the panel
iconThe icon displayed both in the menu and in the panel title
orderThe display order of the panel. The built-in Properties, Sort and Filter panels are at 1000, 2000 and 3000
containerA DOM node, attached to the panel body while the panel is open and detached when it is collapsed
componentThe Vue component rendering the panel. Use LiveDataAdvancedPanelExtension, the only supported one

Every property must be set when the panel is registered. All of them except order can be changed afterwards, and the change is reflected in the interface, which is how a counter in the name or the title is kept up to date.

Registering a Displayer, a Filter or a Layout

Vue 3 no longer allows loading a component globally with Vue.component. Live Data provides a component store instead, on which displayers, filters and layouts are registered by name.

componentStore.register(string, string, () => Promise<VueComponent>)

  • kind: string: any of filter, layout, or displayer, defines the type of loaded component
  • name: string: the name of the registered component
  • loader: () => Promise<VueComponent>: a function returning a VueComponent wrapped in a promise. This allows for the lazy loading of components, only when effectively loaded (see load below)

componentStore.load(string, string): Promise<VueComponent>

  • kind: string: the kind of component to load
  • name: string: the name of the component to load
  • returns: a Promise resolving to the component. When nothing is registered under that kind and name yet, the Promise stays pending and is resolved as soon as a matching component is registered

The store is imported from the @xwiki/platform-livedata-componentstore module, mapped in the importmap of the Live Data webjar:

import { componentStore } from "@xwiki/platform-livedata-componentstore";

// Dynamically register a new "toggle" displayer component.
componentStore.register("displayer", "toggle", async () => {
  return (await import("./components/DisplayerToggle.vue")).default;
});

FAQ

Do I have to implement writing to add a source?

No. save and remove have default implementations throwing UnsupportedOperationException, giving a read-only Live Data.

Can a source be written in a wiki page?

No, a source is a Java Component; the liveTable source is the way round it, turning any live table results page into a source.

Is the panel component API stable?

Only LiveDataAdvancedPanelExtension is. Any other Vue component may be affected by a future change of the widget's internals.

My displayer is never displayed. What happened?

Most likely it was registered under a different kind or name than the configuration asks for. load does not fail in that case: it returns a Promise that stays pending until something is registered under the name it was asked for.

Related

Get Connected