Install
$ agentstack add skill-macareuxdigital-concretecms-skills-concrete-dashboard-crud ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Concrete CMS Dashboard CRUD Implementation
This skill provides instructions and best practices for implementing a CRUD (Create, Read, Update, Delete) interface for Doctrine entities within the Concrete CMS Dashboard, following the standard core patterns.
Architecture Overview
A standard CRUD interface in Concrete CMS Dashboard consists of several layers:
- Doctrine Entity: The data model.
- Search Infrastructure:
- ItemList: Extends
\Concrete\Core\Search\ItemList\Database\ItemList. Handles querying the database. - SearchProvider: Extends
\Concrete\Core\Search\Provider\AbstractSearchProvider. Provides the search configuration. - ColumnSet: Extends
\Concrete\Core\Search\ColumnSet\ColumnSet. Defines table columns. - Result: Extends
\Concrete\Core\Search\Result\Result. Handles result rendering.
- Dashboard Page Controller: Extends
DashboardPageController. Manages actions (view, add, edit, submit, delete). - UI Elements:
- Single Page View: The main template.
- Search Header: An element for the search bar.
- Header Menu: An element for the "Add" button and pagination settings.
1. Search Infrastructure
ItemList
Always extend \Concrete\Core\Search\ItemList\Database\ItemList. Do not use EntityItemList as it is rarely used in core and less flexible.
namespace YourNamespace\Search\ItemList;
use Concrete\Core\Search\ItemList\Database\ItemList;
use Concrete\Core\Search\Pagination\PaginationProviderInterface;
use Pagerfanta\Doctrine\DBAL\QueryAdapter;
class YourEntityList extends ItemList implements PaginationProviderInterface
{
protected function createQuery()
{
$this->query->select('e.id')->from('YourEntityTable', 'e');
return $this->query;
}
public function getResult($queryRow)
{
$em = \ORM::entityManager();
return $em->find('YourNamespace\Entity\YourEntity', $queryRow['id']);
}
public function getPaginationAdapter()
{
return new QueryAdapter($this->deliverQueryObject());
}
public function filterByKeywords($keywords)
{
$this->query->andWhere($this->query->expr()->like('e.name', $this->query->createNamedParameter('%' . $keywords . '%')));
}
}
SearchProvider
namespace YourNamespace\Search\SearchProvider;
use Concrete\Core\Search\Provider\AbstractSearchProvider;
use YourNamespace\Search\ItemList\YourEntityList;
use YourNamespace\Search\ColumnSet\YourEntityColumnSet;
class YourEntitySearchProvider extends AbstractSearchProvider
{
public function getCustomAttributeKeys() { return []; }
public function getBaseColumnSet() { return new YourEntityColumnSet(); }
public function getCurrentColumnSet() { return new YourEntityColumnSet(); }
public function getDefaultColumnSet() { return new YourEntityColumnSet(); }
public function getItemList()
{
return new YourEntityList();
}
}
2. Dashboard Controller
The controller should manage the search state and pass necessary variables to elements.
public function view()
{
$provider = $this->app->make(YourEntitySearchProvider::class);
$list = $provider->getItemList();
if ($this->request->query->has('keywords')) {
$list->filterByKeywords($this->request->query->get('keywords'));
}
$itemsPerPage = $this->request->query->get('itemsPerPage', 10);
$list->setItemsPerPage($itemsPerPage);
$result = $provider->createSearchResultObject($provider->getDefaultColumnSet(), $list);
$headerMenu = $this->app->make(ElementManager::class)->get('your_package/search/menu', 'your_package_handle');
$headerMenu->set('result', $result);
$headerMenu->set('itemsPerPage', $itemsPerPage);
$headerMenu->set('itemsPerPageOptions', $provider->getItemsPerPageOptions());
$headerMenu->set('urlHelper', $this->app->make('helper/url'));
$headerSearch = $this->app->make(ElementManager::class)->get('your_package/search/search', 'your_package_handle');
$headerSearch->set('headerSearchAction', $this->action('view'));
$this->set('result', $result);
$this->set('headerMenu', $headerMenu);
$this->set('headerSearch', $headerSearch);
}
3. UI Implementation
Delete Confirmation
To implement a standard Concrete CMS delete confirmation dialog, use ConcreteAlert.confirm() in your search results table.
Controller
In your view() method, ensure the deletion action URL is available.
$this->set('deleteAction', $this->action('delete'));
View Template
Add a hidden form or a data-modal attribute containing the confirmation form for each row.
action('edit', $item->getItem()->getId()) ?>">
getColumns() as $column) { ?>
getColumnValue() ?>
action('delete', $item->getItem()->getId()) . '">'
. app('helper/validation/token')->output('delete', true)
. t('Are you sure you want to delete this item?')
. '';
?>
"
onclick="ccm_deleteItem(this)">
var ccm_deleteItem = function(elem) {
var modal = elem.getAttribute('data-modal');
ConcreteAlert.confirm(modal, function() {
var submitButton = document.querySelector('.ui-dialog button[data-dialog-action]');
submitButton.disabled = true;
var modalForm = document.querySelector('#ccm-popup-confirmation form');
modalForm.submit();
}, 'btn-danger', '');
};
Search Element (search.php)
Use app('helper/form') to get the form helper.
">
text('keywords', ['placeholder' => t('Search')]) ?>
View Template
Use full-width content and standard table classes.
getColumns() as $column) { ?>
getColumnStyleClass() ?>">
getColumnSortURL()) ?>">getColumnTitle() ?>
getItems() as $item) { ?>
action('edit', $item->getItem()->getId()) ?>">
getColumns() as $column) { ?>
getColumnValue() ?>
getPagination()->renderView('dashboard') ?>
Best Practices
- CSRF Protection: Always use
$this->token->validate('action_name')insubmitanddeletemethods. - Deletion Safety: Before deleting an entity, check for references in other tables and add a descriptive error if deletion is blocked.
- Redirect after Post: Always return a redirect after successful
submitordelete. - Coding Standards: Run
concrete/bin/concrete c5:phpcs fix {path}on your PHP classes. - Element Manager: Use
ElementManagerto load header search and menu elements instead of creating newElementobjects manually.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: MacareuxDigital
- Source: MacareuxDigital/concretecms-skills
- License: MIT
- Homepage: https://forums.concretecms.org/t/proposal-community-maintained-skills-for-coding-agents-in-the-concrete-cms-ecosystem/9764
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.