Book Group Management

Book group management lets a book superuser — a user who is allowed to manage a particular book — create groups and assign users to them, scoped to that one book. It gives book owners a self-service way to manage who can read their book without needing a global administrator for every change.

The feature is available only to users who are allowed to manage the book. Ordinary readers never see the Groups tab or the endpoints described below.

Overview

A book superuser can:

  • Create groups that are scoped to a single book.

  • Search for users by name or email address.

  • Assign found users to one of the book’s groups.

  • Remove members from a group.

Groups created through this feature automatically grant read access to the book, so adding a user to a group is all that is needed to let them read it.

How Groups Work

A few rules govern how book groups are named and managed. They keep group ids stable (so permissions survive edits) while letting titles stay readable.

  • You only provide a title. When creating a group you enter a short title such as Members. The system generates the underlying group id for you.

  • The title is prefixed with the book title. A group you title Members on a book titled My book is stored as My book - Members.

  • The id is generated and frozen. The id is derived from the book title and your title (for example my-book-members). It is fixed at creation time and made unique — if that id already exists, a numeric suffix is appended (my-book-members-2, and so on). The id never changes afterwards.

  • Renaming the book updates group titles automatically. If the book is renamed, every group title is re-prefixed with the new book title. The group id stays stable, so existing permissions and member assignments are preserved.

  • Scope is limited to the book. A superuser can only manage groups that were created through this feature on that book. Groups belonging to other books, and groups created by any other means, are never listed or editable here.

  • Groups cannot be deleted here. Deleting a group is deliberately reserved for global administrators. There is no delete-group action or endpoint in this feature (see No group deletion).

Keycloak-managed sites

On sites where groups are managed in Keycloak (Keycloak group sync is enabled), book groups are created in Keycloak rather than only locally:

  • Creating a group creates it in Keycloak using the book-prefixed title as the Keycloak group name (numbered — My book - Members (2) — if that name is already taken). The local Plone group is the standard Keycloak-synced shadow.

  • Assigning or removing a member propagates to the Keycloak group as well as the local group.

  • Renaming the book renames the Keycloak group so the titles keep following the book title.

Everything else — the Groups tab, the endpoints below, and their responses — behaves the same. If Keycloak cannot be reached, group creation fails and nothing is created locally (the REST API responds with 503); the superuser is asked to try again or contact an administrator.

The Groups Tab

For users who are allowed to manage the book, a Groups tab appears on the book at /<book>/book-groups. From this tab a superuser can:

  1. Enter a title to create a new group for the book.

  2. Search for users by name or email address.

  3. Assign a found user to one of the book’s groups.

  4. Remove a member from a group.

The tab is hidden from users who are not allowed to manage the book.

REST API

All endpoints operate on a book object and are available only to users who are allowed to manage that book.

GET @book-groups

Lists the groups created for this book, each with its members.

const response = await fetch('https://example.com/my-book/@book-groups', {
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer <token>',
  },
});
import requests

response = requests.get(
    'https://example.com/my-book/@book-groups',
    headers={
        'Accept': 'application/json',
        'Authorization': 'Bearer <token>',
    },
)

Response:

{
    "@id": "https://example.com/my-book/@book-groups",
    "items": [
        {
            "@id": "https://example.com/my-book/@book-groups/my-book-members",
            "id": "my-book-members",
            "title": "My book - Members",
            "user_title": "Members",
            "members": [
                {
                    "id": "jane.doe",
                    "fullname": "Jane Doe",
                    "email": "jane@example.com"
                }
            ]
        }
    ],
    "items_total": 1
}

The title is the full, book-prefixed group title; user_title is the plain title the superuser entered when creating the group.

POST @book-groups

Creates a new group for the book. You provide only the title; the id and the book-prefixed title are generated for you. The response is the created group object (same shape as an entry in the list above) with an empty members list.

const response = await fetch('https://example.com/my-book/@book-groups', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <token>',
  },
  body: JSON.stringify({
    title: 'Members',
  }),
});
import requests

response = requests.post(
    'https://example.com/my-book/@book-groups',
    headers={
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        'Authorization': 'Bearer <token>',
    },
    json={
        'title': 'Members',
    },
)

Response (201):

{
    "@id": "https://example.com/my-book/@book-groups/my-book-members",
    "id": "my-book-members",
    "title": "My book - Members",
    "user_title": "Members",
    "members": []
}

Request Body

Field

Required

Description

title

Yes

The plain group title (for example Members). It is prefixed with the book title in the stored group title. Must not be empty or whitespace-only.

Error Responses

Status

Error message

Cause

400

Property 'title' is required

The title field is missing or blank.

503

Could not create group in Keycloak

On a Keycloak-managed site, the group could not be created in Keycloak (e.g. Keycloak unreachable). Nothing is created locally.

POST @book-groups/<group-id>/members

Adds a member to one of the book’s groups. Identify the user by their user id, or by their email address when the site manages users via an external identity provider. The response is the updated group object.

const response = await fetch(
  'https://example.com/my-book/@book-groups/my-book-members/members',
  {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'Authorization': 'Bearer <token>',
    },
    body: JSON.stringify({
      user: 'jane.doe',
    }),
  },
);
import requests

response = requests.post(
    'https://example.com/my-book/@book-groups/my-book-members/members',
    headers={
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        'Authorization': 'Bearer <token>',
    },
    json={
        'user': 'jane.doe',
    },
)

Response:

{
    "@id": "https://example.com/my-book/@book-groups/my-book-members",
    "id": "my-book-members",
    "title": "My book - Members",
    "user_title": "Members",
    "members": [
        {
            "id": "jane.doe",
            "fullname": "Jane Doe",
            "email": "jane@example.com"
        }
    ]
}

Request Body

Field

Required

Description

user

Yes

The user id of the member to add, or their email address when users are managed via an external identity provider.

Error Responses

Status

Error message

Cause

404

Group not found

<group-id> is not a group managed for this book.

400

(message describing the lookup)

No user could be found for the given id or email address.

DELETE @book-groups/<group-id>/members/<user-id>

Removes a member from one of the book’s groups. The response is the updated group object.

const response = await fetch(
  'https://example.com/my-book/@book-groups/my-book-members/members/jane.doe',
  {
    method: 'DELETE',
    headers: {
      'Accept': 'application/json',
      'Authorization': 'Bearer <token>',
    },
  },
);
import requests

response = requests.delete(
    'https://example.com/my-book/@book-groups/my-book-members/members/jane.doe',
    headers={
        'Accept': 'application/json',
        'Authorization': 'Bearer <token>',
    },
)

Response:

{
    "@id": "https://example.com/my-book/@book-groups/my-book-members",
    "id": "my-book-members",
    "title": "My book - Members",
    "user_title": "Members",
    "members": []
}

Error Responses

Status

Error message

Cause

404

Group not found

<group-id> is not a group managed for this book.

No group deletion

There is deliberately no endpoint (and no UI action) for deleting a group through this feature. A book superuser can create groups and manage their members, but removing a group entirely is reserved for global administrators.