PHPackages                             atwx/silverstripe-cms-popup - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Admin Panels](/categories/admin)
4. /
5. atwx/silverstripe-cms-popup

ActiveSilverstripe-vendormodule[Admin Panels](/categories/admin)

atwx/silverstripe-cms-popup
===========================

Generic CMS modal popup for SilverStripe 6 with React content components

1.0.0(2mo ago)082↓90%MITJavaScriptPHP ^8.2CI passing

Since Apr 5Pushed 2mo agoCompare

[ Source](https://github.com/atwx/silverstripe-cms-popup)[ Packagist](https://packagist.org/packages/atwx/silverstripe-cms-popup)[ RSS](/packages/atwx-silverstripe-cms-popup/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (1)Dependencies (5)Versions (3)Used By (0)

This project has moved. The management was taken over by [S2-Hub](https://s2-hub.com). Development will continue here: [s2hub/silverstripe-cms-popup](https://github.com/s2hub/silverstripe-cms-popup)
======================================================================================================================================================================================================

[](#this-project-has-moved-the-management-was-taken-over-by-s2-hub-development-will-continue-here-s2hubsilverstripe-cms-popup)

Opens modal dialogs in the SilverStripe CMS from a button in the form action menu. Four built-in content types: **Search** (search with result selection), **Batch** (sequential processing of a queue), **Content** (generic HTML loader), and **FormSchema** (full SilverStripe form including HTMLEditorField).

**Requirements:** SilverStripe 6, PHP 8.2+

---

Installation
------------

[](#installation)

```
composer require atwx/silverstripe-cms-popup
```

The module registers itself automatically in `LeftAndMain`. No additional YAML configuration required.

---

Search modal
------------

[](#search-modal)

Opens a dialog with a server-rendered search form. The user enters search terms; results are loaded via AJAX. Clicking a result fires a `cms-modal:select` event on the triggering button.

Create a handler class that extends `CmsPopupSearchHandler`:

```
use Atwx\CmsPopup\Handler\CmsPopupSearchHandler;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;

class MySearchHandler extends CmsPopupSearchHandler
{
    public function getSearchFormFields(): FieldList
    {
        return FieldList::create(
            TextField::create('q', 'Search')->setAttribute('autofocus', 'autofocus'),
        );
    }

    public function search(string $query, HTTPRequest $request): string
    {
        $results = MyRecord::get()->filter('Title:PartialMatch', $query);
        $html = '';
        foreach ($results as $record) {
            $payload = htmlspecialchars(json_encode(['id' => $record->ID, 'title' => $record->Title]), ENT_QUOTES);
            $html .= "{$record->Title}";
        }
        return $html ?: 'No results found.';
    }

    // Optional: override minimum query length (default: 2)
    public function getMinQueryLength(): int { return 1; }
}
```

```
use Atwx\CmsPopup\Forms\CmsModalSearchAction;

$action = CmsModalSearchAction::forHandler(MySearchHandler::class)
    ->setModalTitle('Select a record')
    ->setModalSize('md');
```

The search endpoint is routed automatically via `CmsPopupSearchRouterController` at `cms-search/`.

### autoSearch and initialQuery

[](#autosearch-and-initialquery)

- **`autoSearch`** *(bool, default `true`)* — triggers a search immediately when the modal opens
- **`initialQuery`** *(string, optional)* — pre-fills the query and is used for the initial search

```
$action = CmsModalSearchAction::forHandler(MySearchHandler::class)
    ->setModalTitle('Select a record')
    ->setModalData([
        'autoSearch'   => true,
        'initialQuery' => 'some term',
    ]);
```

### Listening to the select event

[](#listening-to-the-select-event)

```
document.querySelector('.my-trigger-button').addEventListener('cms-modal:select', (e) => {
    const { id, title } = e.detail;
    // Write the value into a form field, etc.
});
```

---

Batch modal
-----------

[](#batch-modal)

Opens a dialog with a configuration form. After clicking "Start", queue items are sent one by one to an action endpoint; progress is displayed live.

```
use Atwx\CmsPopup\Handler\CmsPopupBatchHandler;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Forms\FieldList;

class MyBatchHandler extends CmsPopupBatchHandler
{
    public function getBatchFormFields(): FieldList
    {
        return FieldList::create(/* your fields */);
    }

    public function getQueueItems(HTTPRequest $request): array
    {
        return [
            ['id' => 1, 'title' => 'Item A'],
            ['id' => 2, 'title' => 'Item B'],
        ];
    }

    public function processItem(HTTPRequest $request): HTTPResponse
    {
        $body = json_decode($request->getBody(), true);
        $record = MyRecord::get()->byID($body['id']);
        if (!$record) {
            return CmsPopupBatchResponse::error('Not found');
        }
        $record->doSomething();
        return CmsPopupBatchResponse::success('Done');
    }
}
```

```
use Atwx\CmsPopup\Forms\CmsModalBatchAction;

$action = CmsModalBatchAction::forHandler(MyBatchHandler::class, ['pageID' => $this->ID])
    ->setModalTitle('Run batch')
    ->setSubmitLabel('Start');
```

The batch endpoints are routed automatically via `CmsPopupBatchRouterController` at `cms-batch/`.

### Queue endpoint

[](#queue-endpoint)

`getQueueItems()` is called when a field named `recursive` is checked in the batch form. Items are merged with any static queue set via `setBaseQueue()`. Items with `"enabled": false` are skipped.

### Action endpoint

[](#action-endpoint)

`processItem()` is called via `POST` for each queue item. The request body contains the queue item merged with the form values as JSON:

```
{ "id": 1, "title": "Item A", "recursive": true }
```

### Response helpers

[](#response-helpers)

```
use Atwx\CmsPopup\Http\CmsPopupBatchResponse;
use Atwx\CmsPopup\Http\CmsPopupBatchDetail;

return CmsPopupBatchResponse::success('Processed', [
    CmsPopupBatchDetail::info('en_US', 'OK'),
]);
return CmsPopupBatchResponse::warning('Skipped', []);
return CmsPopupBatchResponse::error('Failed', [
    CmsPopupBatchDetail::error('de_DE', 'API error'),
]);
```

MethodHTTP statusModal display`success($message, $details)`200green check`warning($message, $details)`200yellow warning`error($message, $details)`422red crossDetail severity: `info` (green), `warning` (yellow), `error` (red).

---

FormSchema modal
----------------

[](#formschema-modal)

Opens a full SilverStripe form in the modal using `FormBuilderLoader`. Supports all CMS field types including `HTMLEditorField` (TinyMCE). The modal closes automatically after a successful save.

Create a handler class that extends `CmsPopupHandler`:

```
use Atwx\CmsPopup\Handler\CmsPopupHandler;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
use SilverStripe\ORM\DataObject;

class MyRecordPopup extends CmsPopupHandler
{
    public function getRecord(HTTPRequest $request): DataObject
    {
        return MyRecord::get()->byID((int) $request->getVar('recordID'));
    }

    public function getFields(DataObject $record): FieldList
    {
        return FieldList::create(
            HTMLEditorField::create('Content', 'Content')->setRows(8),
        );
    }

    // Optional overrides:
    // public function save(DataObject $record, array $data, Form $form): void { ... }
    // public function canAccess(DataObject $record): bool { return $record->canEdit(); }
}
```

```
use Atwx\CmsPopup\Forms\CmsModalFormSchemaAction;

$action = CmsModalFormSchemaAction::forHandler(MyRecordPopup::class, ['recordID' => $this->ID])
    ->setModalTitle('Edit record')
    ->setModalSize('lg');
```

The form endpoint is routed automatically via `CmsPopupAdminController` at `/admin/cms-popup/popup`.

### GridField column

[](#gridfield-column)

Use `CmsPopupGridFieldColumn` to add a per-row popup button to a GridField:

```
use Atwx\CmsPopup\GridField\CmsPopupGridFieldColumn;

$config->addComponent(new CmsPopupGridFieldColumn(
    MyRecordPopup::class,
    'recordID',           // query parameter name for the record ID
    'Edit record'         // column tooltip / modal title
));
```

After a successful save, the GridField is automatically reloaded.

---

Content modal
-------------

[](#content-modal)

Loads arbitrary HTML from a URL into the dialog.

```
use Atwx\CmsPopup\Forms\CmsModalAction;

$action = CmsModalAction::create('showInfo', 'Show details')
    ->setModalComponent('CmsModalContent')
    ->setModalTitle('Information')
    ->setModalData(['url' => $this->Link('infoHtml')]);
```

---

Button icon
-----------

[](#button-icon)

A SilverStripe admin font-icon class can be added to any action button:

```
$action->setButtonIcon('font-icon-search');
$action->setButtonIcon('font-icon-sync');
```

---

Modal sizes
-----------

[](#modal-sizes)

ValueWidth`sm`480 px`md`640 px (default)`lg`860 px---

Writing the selected value into a form field
--------------------------------------------

[](#writing-the-selected-value-into-a-form-field)

Typical pattern: open search modal → write selected value into a hidden field → save the form.

```
$hiddenId    = HiddenField::create('MyRecordID');
$hiddenTitle = ReadonlyField::create('MyRecordTitle', 'Selected record');

$search = CmsModalSearchAction::forHandler(MySearchHandler::class)
    ->setModalTitle('Choose record');
```

```
button.addEventListener('cms-modal:select', (e) => {
    document.querySelector('[name=MyRecordID]').value = e.detail.id;
    document.querySelector('[name=MyRecordTitle]').value = e.detail.title;
});
```

---

Custom content components
-------------------------

[](#custom-content-components)

Register a custom React component via the SilverStripe Injector:

```
import Injector from 'lib/Injector';
import MyCustomModal from './MyCustomModal';

Injector.component.register('MyCustomModal', MyCustomModal);
```

```
$action->setModalComponent('MyCustomModal');
$action->setModalData(['someParam' => 'value']);
```

The component receives the props `data` (from `setModalData()`), `onClose`, `onSelect`, and `onSaved`.

---

Building assets
---------------

[](#building-assets)

```
cd vendor/atwx/silverstripe-cms-popup
npm install
npm run build   # production
npm run dev     # development
npm run watch   # watch mode
```

Output: `client/dist/js/bundle.js` and `client/dist/css/cms-popup.css`.

###  Health Score

39

—

LowBetter than 85% of packages

Maintenance85

Actively maintained with recent releases

Popularity9

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 55.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

81d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/64d979993815fa2d2ea95b3fd72b4f43e95a631ce569f4f1c0330786971b5fc2?d=identicon)[adiwidjaja](/maintainers/adiwidjaja)

---

Top Contributors

[![adiwidjaja](https://avatars.githubusercontent.com/u/280394?v=4)](https://github.com/adiwidjaja "adiwidjaja (10 commits)")[![claude](https://avatars.githubusercontent.com/u/81847?v=4)](https://github.com/claude "claude (7 commits)")[![Copilot](https://avatars.githubusercontent.com/in/1143301?v=4)](https://github.com/Copilot "Copilot (1 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisRector

Code StylePHP\_CodeSniffer

### Embed Badge

![Health badge](/badges/atwx-silverstripe-cms-popup/health.svg)

```
[![Health](https://phpackages.com/badges/atwx-silverstripe-cms-popup/health.svg)](https://phpackages.com/packages/atwx-silverstripe-cms-popup)
```

###  Alternatives

[silverstripe/cms

The SilverStripe Content Management System

5163.5M1.3k](/packages/silverstripe-cms)[silverstripe/admin

SilverStripe admin interface

262.7M374](/packages/silverstripe-admin)[colymba/gridfield-bulk-editing-tools

Silverstripe CMS GridField component to upload images/files and edit records in bulk

88689.3k41](/packages/colymba-gridfield-bulk-editing-tools)[silverstripe/lumberjack

A module to make managing pages in a GridField easy without losing any of the functionality that you're used to in the CMS.

341.2M41](/packages/silverstripe-lumberjack)[plastyk/dashboard

An extendable dashboard for Silverstripe

2244.2k2](/packages/plastyk-dashboard)[silverstripe/superglue

102.3k](/packages/silverstripe-superglue)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
