Custom Value List Providers
By default the Selections plugin reads its options from Value List documents in the repository. A custom value list provider replaces that source with your own code, for example a REST service, a database, or a computed list.
through a single registry that is shared by the legacy CMS document editor and the Experience
Manager (visual editor), so one registration serves both editors.
Choose an interface
| Interface | Use for | Notes |
|---|---|---|
| org.onehippo.forge.selection.frontend.provider.ValueListProvider | New code (recommended) | Wicket-free. Receives a JCR Session as a method argument. |
| org.onehippo.forge.selection.frontend.provider.IValueListProvider | Existing code | Extends Wicket's IClusterable, so it drags Wicket onto the classpath. Kept for backward compatibility. |
A minimal provider:
package com.example;
import java.util.Locale;
import javax.jcr.Session;
import org.onehippo.forge.selection.frontend.model.ListItem;
import org.onehippo.forge.selection.frontend.model.ValueList;
import org.onehippo.forge.selection.frontend.provider.ValueListProvider;
public class CountryValueListProvider implements ValueListProvider {
@Override
public ValueList getValueList(final String name, final Locale locale, final Session session) {
final ValueList list = new ValueList();
list.add(new ListItem("nl", "Netherlands"));
list.add(new ListItem("be", "Belgium"));
return list;
}
}
Requirements:
- a public no-argument constructor — the provider is instantiated reflectively;
- no Wicket API calls and no UserSession.get() — the provider is invoked outside a Wicket
request cycle. Use the session argument for repository access; - the name argument is the field's source property, passed through unchanged. It may be
empty when the field has no source; - never return null; return an empty ValueList instead.
Results of custom providers are not cached by the platform. A provider backed by a slow external system is responsible for its own caching and invalidation.
Optional configuration
Implement configure(Node) to read configuration from the provider's own JCR node. It is called once, after instantiation and before registration.
@Override
public void configure(final Node providerNode) throws RepositoryException {
if (providerNode.hasProperty("endpoint")) {
this.endpoint = providerNode.getProperty("endpoint").getString();
}
}
Register the provider
Add a child node under the value list service's module configuration. The providers node ships with the platform; only the child node is project configuration.
definitions:
config:
/hippo:configuration/hippo:modules/valuelist-service/hippo:moduleconfig/providers/service.valuelist.countries:
jcr:primaryType: hipposys:moduleconfig
class: com.example.CountryValueListProvider
endpoint: https://example.com/api/countries # optional, read via configure(Node)
- The node name is the provider's service name (service.valuelist.countries above). Use it as the value of the field's valuelistProvider property.
- class is required and must implement ValueListProvider (or IValueListProvider).
- service.valuelist.default is reserved for the built-in Value List document provider and cannot be used as a custom name.
- Adding, changing, or removing a provider node takes effect without a repository restart. Deploying a changed provider class requires a redeploy.
Use the provider on a selection field
In the Document Type Editor, set the valuelistProvider property of a Dynamic Dropdown, Radio Group, or multi-select field to the provider's service name. In configuration that is the field's cluster.options node:
/hippo:namespaces/myproject/newsdocument/editor:templates/_default_/country:
jcr:primaryType: frontend:plugin
# ... field wiring omitted ...
/cluster.options:
jcr:primaryType: frontend:pluginconfig
valuelistProvider: service.valuelist.countries
source is optional when a custom provider is configured; when present it is passed to the provider as the name argument.
The dotted key valuelist.provider is a deprecated alias of valuelistProvider. It is still honoured (the canonical key wins when both are present) and logs a deprecation warning. Rename it to valuelistProvider.
Legacy registration under cms-services
Providers registered the classic way keep working:
/hippo:configuration/hippo:frontend/cms/cms-services/countryValueListProvider: jcr:primaryType: frontend:plugin plugin.class: com.example.CountryValueListProvider valuelist.provider: service.valuelist.countries
Here valuelist.provider is the service name the provider registers under, not the deprecated field property of the same name. Entries are picked up automatically, provided the class can be instantiated through a public no-argument constructor. If the same service name exists in both locations, the moduleconfig/providers entry wins.
A provider that extends org.hippoecm.frontend.plugin.Plugin (including subclasses of DocumentValueListProvider) cannot have a no-argument constructor, so it cannot be instantiated this way. Such providers keep working in the legacy CMS document editor; the selection field plugins fall back to the Wicket plugin context (restored in 16.9.4 and 17.2.0), but they are invisible to the Experience Manager, where the field renders an empty list. To support both editors, migrate them as shown below.
Editor support
| Registration | Provider shape | Legacy CMS document editor | Experience Manager |
|---|---|---|---|
| moduleconfig/providers | POJO (ValueListProvider / IValueListProvider) | Yes | Yes |
| cms-services | POJO with public no-arg constructor | Yes | Yes |
| cms-services | extends Plugin (legacy) | Yes | No — empty list |
| none (default) | built-in Value List document | Yes | Yes |
Upgrading an existing provider
Before — a Wicket Plugin provider, working in the legacy editor only:
public class CountryValueListProvider extends Plugin implements IValueListProvider {
public CountryValueListProvider(final IPluginContext context, final IPluginConfig config) {
super(context, config);
context.registerService(this, config.getString(IValueListProvider.SERVICE));
}
@Override
public ValueList getValueList(final String name, final Locale locale) {
final Session session = UserSession.get().getJcrSession();
return buildList(session);
}
@Override
public List<String> getValueListNames() {
return Collections.singletonList("countries");
}
}
After — a POJO provider, working in both editors:
public class CountryValueListProvider implements ValueListProvider {
@Override
public ValueList getValueList(final String name, final Locale locale, final Session session) {
return buildList(session);
}
}
Three mechanical changes:
- drop extends Plugin and the (IPluginContext, IPluginConfig) constructor. No explicit service registration is needed, the configuration node is the registration;
- take the JCR session from the method argument instead of UserSession.get();
- move plugin-config reads into configure(Node).
getValueListNames() is not part of ValueListProvider and can be removed.
Bootstrap — replace the cms-services node with a providers node:
# remove /hippo:configuration/hippo:frontend/cms/cms-services/countryValueListProvider: jcr:primaryType: frontend:plugin plugin.class: com.example.CountryValueListProvider valuelist.provider: service.valuelist.countries # add /hippo:configuration/hippo:modules/valuelist-service/hippo:moduleconfig/providers/service.valuelist.countries: jcr:primaryType: hipposys:moduleconfig class: com.example.CountryValueListProvider
The service name stays the same, so no document type configuration has to change. Rename any valuelist.provider field property to valuelistProvider while you are there.
Troubleshooting
| Log message | Cause |
|---|---|
| Provider '<name>' class '<fqcn>' is not Experience Manager compatible | No public no-argument constructor (typically still extends Plugin), or the class failed to load. The provider is skipped. |
| Provider '<name>' class '<fqcn>' does not implement ValueListProvider | Wrong class in class / plugin.class. |
| Skipping provider '<name>': missing 'class' property | A providers child node without a class property. |
| No ValueListProvider registered under name '<name>'; returning empty list | The field references a service name that is not registered — check for a typo, or the provider was skipped at startup for one of the reasons above. |
| Plugin config uses deprecated key 'valuelist.provider'; rename it to 'valuelistProvider'. | Field still uses the deprecated dotted key. |
An empty dropdown in the Experience Manager while the legacy editor works correctly almost always means the provider is a Plugin subclass registered under cms-services. See Upgrading an existing provider above.