Skip to content

Integrating the Plone tools

This guide explains how to add plone_soliplex_tool.js to a Plone 6 site so the Soliplex chat widget can search content, identify the logged-in user, and answer questions such as "What are the most recent changes in my folder?".

It covers both flavours of Plone 6:

  • Classic UI — the server-rendered interface.
  • Volto — the React frontend.

For the widget's general options and the list of tools/API it exposes, see the Widget Usage Guide (in particular the Plone Integration reference section).

What you get

plone_soliplex_tool.js is a self-contained script (no build step, no dependencies). Once loaded it exposes window.PloneSoliplex with:

  • PloneSoliplex.getToolDefinitions() — the agent tools to pass to SoliplexChat.init({ tools }).
  • PloneSoliplex.configure(options) — override defaults (site root, token key, member-folder base, etc.).
  • Direct client helpers (getCurrentUser, search, listFolderContents, …).

The provided tools are: plone_get_current_user, plone_recent_changes_in_my_folder, plone_search, plone_list_folder_contents, and plone_get_content.

Prerequisites

  • Plone 6 with plone.restapi installed and enabled (it ships with Plone 6). The tools call the standard @search, @users, and content endpoints.
  • A reachable Soliplex server with at least one room.
  • The two front-end files, served over HTTP(S):
  • soliplex-chat.js — the widget bundle
  • plone_soliplex_tool.js — the Plone tools
  • (Only if your Soliplex server itself requires sign-in) soliplex-auth-callback.html, hosted next to the page that embeds the widget. See Authentication for details.

⚠️ Cookie sessions need a "current user" endpoint you must add yourself. With Plone's default authentication the browser session lives in the __ac cookie, which is HttpOnly and opaque — so client-side JavaScript cannot read the logged-in user's id from it. plone.restapi does not ship an endpoint that returns the authenticated user for a cookie session, so the tools cannot identify the user (plone_get_current_user, plone_recent_changes_in_my_folder, etc. will report the user as unknown) unless you provide one.

Add a small REST service — the tools call @logged-in-user by default (see loggedInUserEndpoint) and expect it to return { userid, fullname, email } for an authenticated session, or a 401 when anonymous.

This does not apply if a Volto/JWT token is present (the id is read from the token's sub claim) or if you set userId / getUserId explicitly via PloneSoliplex.configure(...).

Adding the @logged-in-user endpoint

A minimal implementation registers a GET service on the site root. A complete, working example lives at /trabajo/runyan/ESP/ploud/backend/sources/esp-library/src/esp/library/restapi — reproduced here:

# logged_in_user.py
from plone import api
from plone.restapi.services import Service


class LoggedInUserGet(Service):
    """Return the userid, fullname and email of the currently logged in user."""

    def reply(self):
        user = api.user.get_current()
        if user is None or api.user.is_anonymous():
            self.request.response.setStatus(401)
            return {
                "error": {
                    "type": "Unauthorized",
                    "message": "No user is logged in.",
                }
            }

        return {
            "userid": user.getId(),
            "fullname": user.getProperty("fullname") or "",
            "email": user.getProperty("email") or "",
        }
<!-- configure.zcml -->
<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:plone="http://namespaces.plone.org/plone">

  <plone:service
      method="GET"
      factory=".logged_in_user.LoggedInUserGet"
      for="plone.base.interfaces.IPloneSiteRoot"
      permission="zope2.View"
      name="@logged-in-user"
      />

</configure>

Once this ships in an add-on installed on the site, the client resolves the current user from the cookie session automatically. If you name the endpoint differently, point the tools at it with PloneSoliplex.configure({ loggedInUserEndpoint: "@your-endpoint" }).

Step 1: Make the scripts available to your site

Both scripts must be reachable from the browser. For the smoothest authentication story, serve them from the same origin as your Plone site so the tools can reuse the browser's existing Plone session (see Authentication below).

Pick one:

  • Bundle them with a theme/add-on (recommended — see the per-UI steps below). The files end up served from your Plone origin.
  • Serve from a CDN or the Soliplex server. This works too, but if the scripts are on a different origin than Plone, authentication must go through a JWT or an explicit token rather than session cookies.

Step 2: Load and initialize the widget

Classic UI

The reliable way to inject scripts into every page in Classic UI is through a custom Diazo theme.

  1. Copy soliplex-chat.js and plone_soliplex_tool.js into your theme folder (the same place as the theme's index.html, e.g. a js/ subfolder). They are then served at ++theme++<your-theme>/js/soliplex-chat.js.

  2. In the theme's index.html, add the scripts and an init block before </body>:

<script src="++theme++your-theme/js/soliplex-chat.js"></script>
<script src="++theme++your-theme/js/plone_soliplex_tool.js"></script>
<script>
  // The Plone site root is auto-detected; override it if needed.
  PloneSoliplex.configure({
    // baseUrl: "https://plone.example.com",
    // memberFolderBase: "/Members",
  });

  SoliplexChat.init({
    baseUrl: "https://soliplex.example.com",
    roomId: "assistant",
    title: "Plone Assistant",
    tools: PloneSoliplex.getToolDefinitions(),
  });
</script>

Diazo passes the theme's own <script> tags through to the rendered page, so the widget loads on every themed page.

Alternative (resource registry): instead of a theme you can register the two files as a bundle in plone.bundles (via plone.staticresources or a registry.xml in an add-on) and add the init <script> through a viewlet. This is more work but keeps the assets out of the theme.

Volto

Volto is a separate React app, so the scripts are added to its HTML shell rather than to a Plone template. Use a small Volto add-on (or your project) and either of these approaches.

Option A — customize the HTML shell (Html.jsx). Shadow src/components/theme/Html/Html.jsx and add the tags. First place the files in Volto's public/ folder (served from the site root):

public/
├── soliplex-chat.js
└── plone_soliplex_tool.js

Then, in your shadowed Html.jsx, add to <body>:

<script src="/soliplex-chat.js" />
<script src="/plone_soliplex_tool.js" />
<script
  dangerouslySetInnerHTML={{
    __html: `
      window.addEventListener('load', function () {
        SoliplexChat.init({
          baseUrl: 'https://soliplex.example.com',
          roomId: 'assistant',
          title: 'Plone Assistant',
          tools: PloneSoliplex.getToolDefinitions(),
        });
      });
    `,
  }}
/>

Option B — inject via appExtras. Register a component in config.settings.appExtras that uses <Helmet> to add the two <script> tags and the init snippet. This keeps everything inside your add-on without shadowing Html.jsx.

Cross-origin note for Volto: the Volto frontend and the plone.restapi backend are usually on different origins. Set PloneSoliplex.configure({ baseUrl: '<your-plone-backend-url>' }) and rely on the Volto JWT (stored in localStorage under auth_token, which the tools read by default). Make sure the backend's CORS policy allows requests from the Volto origin.

Step 3: Authentication

The Plone tools authenticate the same way the browser already does, in this order:

  1. Volto/Plone JWT — if a token is present in localStorage (default key auth_token) it is sent as Authorization: Bearer ….
  2. Session cookies — otherwise, same-origin requests include cookies (credentials: "include"), which covers Plone Classic logins.
  3. Soliplex OIDC token — as a last resort the widget's own token is reused (handy when Plone and Soliplex share an identity provider).

For cookie-only Classic sessions the user id can't be read on the client (the __ac cookie is HttpOnly). The client then asks the server via the @logged-in-user endpoint — which plone.restapi does not provide, so you must add it yourself (see Adding the @logged-in-user endpoint). If it isn't installed you can instead supply the id yourself with PloneSoliplex.configure({ userId }). See the Authentication reference in the usage guide, and the PloneSoliplex.configure options table for the full list of knobs (baseUrl, token, tokenKey, useSoliplexToken, userId, getUserId, memberFolderBase, allowWrites, defaultLimit, loggedInUserEndpoint).

This is separate from the Soliplex widget's own sign-in. If your Soliplex server requires login, also host soliplex-auth-callback.html next to the embedding page — see Authentication.

Step 4: Verify

  1. Log in to your Plone site as usual.
  2. Open the chat widget and ask: "Who am I?" — the agent should call plone_get_current_user and return your id, name, and roles.
  3. Ask: "What are the most recent changes in my folder?" — it should list recently modified content.

To debug the raw tool responses, initialize the widget with debug: true (see Configuration Options); the raw JSON returned by each tool is then shown inline in the chat.

Troubleshooting

Symptom Likely cause / fix
PloneSoliplex is not defined plone_soliplex_tool.js didn't load, or loaded after SoliplexChat.init. Ensure both scripts load before the init snippet.
Agent reports the user as unknown No JWT and @logged-in-user not installed. Set PloneSoliplex.configure({ userId }) or install the endpoint.
401/403 on tool calls Requests aren't authenticated. On a different origin than Plone, ensure a JWT is present (tokenKey) and CORS allows the origin.
Requests hit the wrong host Set PloneSoliplex.configure({ baseUrl }) to your Plone site root.
Search/paths return nothing Check memberFolderBase matches where per-user folders live, and that the user has permission to the content.