Integrate Direct Edit Button into Custom Application
Direct Edit can be driven from your own frontend, not only from Pimcore Studio. You supply a permission service and a controller, and build the button yourself.
The bundle no longer ships frontend assets. /bundles/pimcoredirectedit/js/bootstrap-modal.js,
/bundles/pimcoredirectedit/css/style.css and the createDirectEditButton() helper were removed together with the
Admin Classic frontend, see Upgrade Notes. Build your own UI against the
routes below.
1 Create a custom Permission Service
Create a permission service that implements PermissionServiceInterface with these four methods:
getApplicationPrefix(): stringreturns a unique prefix for your application.getUserId(): ?intreturns a unique user id. The interface declares it as?int, so return an integer, not a session string. Direct Edit concatenates it with the application prefix into the token owner, so the pair has to stay stable for the duration of an editing session.mapPimcoreUserId(): intmaps to the Pimcore user that owns the version files. Return0(system user) if your application has no matching Pimcore user.hasAssetPermission(Asset $asset): booldecides whether the current user may edit the asset.
The following example identifies the user through the Symfony security token and lets any authenticated Pimcore user with the Direct Edit permission edit an asset they may publish.
namespace App\DirectEdit\Permission;
use Pimcore\Bundle\DirectEditBundle\Service\Permission\PermissionServiceInterface;
use Pimcore\Bundle\DirectEditBundle\Service\Permission\PimcoreBackendPermissionService;
use Pimcore\Model\Asset;
use Pimcore\Model\User;
use Pimcore\Security\User\User as SecurityUser;
use Symfony\Bundle\SecurityBundle\Security;
class FrontendPermissionService implements PermissionServiceInterface
{
public function __construct(private readonly Security $security)
{
}
public function hasAssetPermission(Asset $asset): bool
{
$user = User::getById($this->mapPimcoreUserId());
if (!$user) {
return false;
}
return $user->isAllowed(PimcoreBackendPermissionService::PERMISSION_NAME)
&& $asset->isAllowed('publish', $user);
}
public function getUserId(): ?int
{
return $this->mapPimcoreUserId() ?: null;
}
public function getApplicationPrefix(): string
{
return 'my_app_';
}
public function mapPimcoreUserId(): int
{
// The Symfony token holds Pimcore\Security\User\User, which wraps the Pimcore\Model\User.
$securityUser = $this->security->getUser();
return $securityUser instanceof SecurityUser ? $securityUser->getUser()->getId() : 0;
}
}
2 Create a new Controller and implement routes
Set a route prefix on your controller and implement the routes. Use FileEditControllerTrait unless you need custom
behaviour. Inject your permission service:
App\Controller\DirectEdit\FrontendDirectEditController:
arguments:
$permissionService: '@App\DirectEdit\Permission\FrontendPermissionService'
<?php
namespace App\Controller\DirectEdit;
use Pimcore\Controller\FrontendController;
use Symfony\Component\Routing\Attribute\Route;
/**
* Class FrontendDirectEditController
* @package App\Controller\DirectEdit
*
*/
#[Route('/frontend_direct_edit')]
class FrontendDirectEditController extends FrontendController
{
use \Pimcore\Bundle\DirectEditBundle\Controller\FileEditControllerTrait;
}
3 Build the Frontend
The bundle does not ship a button or a modal any more. Your frontend calls the routes provided by
FileEditControllerTrait, all of them relative to the prefix you set on your controller (/frontend_direct_edit in the
example above).
| Route | Purpose |
|---|---|
/settings | Serves a JavaScript snippet that sets pimcore.settings.direct_edit.mercure.client_side_url, the hub URL the browser subscribes to. |
/generate_link/{assetId} | Starts an editing session and returns the pimcorefile:// link that opens the desktop client. |
/cancel_edit/{assetId} | Cancels the session, deletes the token and removes the temporary version file. |
/confirm_edit/{assetId} | Applies the uploaded file to the asset, or returns a modal payload when a conflict was detected. |
/confirm_overwrite_after_local_edit/{assetId} | Resolves a conflict by saving the local change over the asset. |
/confirm_versionsave_after_local_edit/{assetId} | Resolves a conflict by saving the local change and then restoring the previously published version, so the local change stays in the version history only. |
/event_server_has_gone/{assetId} | Returns the modal payload shown when the browser lost the Mercure connection. |
None of these routes restrict the HTTP method. Every route except /settings and /event_server_has_gone runs your
permission service against the asset and returns 403 when hasAssetPermission() is false.
The minimal flow is:
- Call
/generate_link/{assetId}, then navigate the browser to thepimcorefile://link it returns. That hands the asset to the desktop client. - Subscribe to the hub URL from
/settings, on the topichttp://www.pimcore.com/direct-edit/client-upload/user/<applicationPrefix><userId>, so the page learns when the client uploads the file. - When the upload arrives, call
/confirm_edit/{assetId}. If the asset changed in Pimcore meanwhile, offer the user the two conflict routes.
The bundle does not hand the browser a Mercure subscriber token. The Admin Classic controller that set the
mercureAuthorization cookie was removed, and /settings returns the hub URL only. Authorize your frontend against the
hub yourself, for example with an endpoint of your own that issues a subscriber JWT.
Guard the button with your permission service, so it only appears for assets the user may edit:
{% if permissionService.hasAssetPermission(asset) %}
<button class="js-direct-edit" data-asset-id="{{ asset.id }}">Edit locally</button>
{% endif %}