Adds a copy/duplicate button to GridFields
Maintained by Restruct. If this module saves you time, you can support ongoing maintenance.
Elvinas Liutkevičius <elvinas (at) unisolutions (dot) eu> (Forked from dhensby's SS4-updated fork for quick maintenance & updates/tags)
- Silverstripe 5 or 6 (
silverstripe/framework) - PHP 8.1 or newer (Silverstripe 6 itself needs 8.3)
composer require restruct/silverstripe-copybutton
| Branch | Module version | Silverstripe | PHP |
|---|---|---|---|
main |
3.x |
^5 || ^6 |
^8.1 |
v2 |
2.0.x |
^4 || ^5 || ^6 (see note) |
not declared |
1, 1.0 |
1.x |
^3 |
not declared |
2.0.1 declares Silverstripe 4, 5 and 6 but throws a Silverstripe 6-only exception class when a
user without create permission triggers a copy, so on 4 and 5 that path fatals (issue #3). 3.x
and the 2.0.2 hotfix fix it. Silverstripe 4 reached end of life in April 2025 and is no longer supported or tested here;
projects still on it should stay on 2.0.x.
main is the maintained line: it supports every Silverstripe version this module still targets.
The v2 branch exists only for hotfixes to projects that stay on ^2.0 (such as 2.0.2).
composer.json is the source of truth for exact constraints; this table is a quick reference.
The component is Unisolutions\GridField\CopyButton. Add it to a GridField's config:
use SilverStripe\Forms\GridField\GridFieldEditButton;
use Unisolutions\GridField\CopyButton;
// As an item in the row's action menu (the "..." dropdown) - the default:
$config->addComponent(CopyButton::create());
// Or as a button in the Actions column, placed before the edit button:
$config->addComponent(CopyButton::create(true), GridFieldEditButton::class);CopyButton::create() and new CopyButton() both work from 3.0 (2.x only supported new).
The one constructor argument is $useAsColumn (default false): false puts "Copy" in the row's
action menu, true renders an icon button in the Actions column instead.
Pass the class name, not a short string, as the second addComponent() argument: GridFieldConfig
places the component before the first existing component that is an instanceof it, and silently
appends it at the end when nothing matches.
In a ModelAdmin, the place to do this is getGridFieldConfig() (or the updateGridFieldConfig
extension hook):
use SilverStripe\Forms\GridField\GridFieldConfig;
use SilverStripe\Forms\GridField\GridFieldEditButton;
use Unisolutions\GridField\CopyButton;
class MyAdmin extends ModelAdmin
{
protected function getGridFieldConfig(): GridFieldConfig
{
$config = parent::getGridFieldConfig();
$config->addComponent(CopyButton::create(), GridFieldEditButton::class);
return $config;
}
}Clicking Copy calls DataObject::duplicate() on the record and writes the copy; the GridField then
re-renders with the copy in the list. No edit form is opened, unless you turn on
open after copy.
- Relations are copied only where the model says so.
duplicate()follows the model'scascade_duplicatesconfig; see cascading duplications. - Only records in the GridField's own list can be copied: the record is looked up in that list, so a request for any other ID does nothing.
- Permissions: the button (column mode) or menu item (menu mode) is only shown to users for whom
the record's
canCreate()is true. A copy request from anyone else is refused with aValidationException("No create permissions") and nothing is written.
To act on the copy - say, to clear some relations - implement onAfterDuplicate() on the model or
in an extension. It runs on the copy, after it was written:
use SilverStripe\Core\Extension;
class SomeObjectExtension extends Extension
{
public function onAfterDuplicate($original, $doWrite, $relations)
{
$this->getOwner()->Members()->removeAll();
}
}From 3.1, the button can open the copy's edit form straight away instead of re-rendering the list. It is off by default:
use SilverStripe\Forms\GridField\GridFieldEditButton;
use Unisolutions\GridField\CopyButton;
$config->addComponent(CopyButton::create()->setOpenAfterCopy(true), GridFieldEditButton::class);The copy opens in the GridField's own detail form (GridFieldDetailForm), at the same URL the row's
edit button links to. That works for a GridField in a ModelAdmin and for one nested in a record's
edit form. In the CMS the redirect is answered with an X-ControllerURL header, and the admin loads
the edit form into the panel; outside the CMS it is a normal 302 redirect.
When the GridField cannot open the copy, the button falls back to re-rendering the list, without an error:
- the GridField has no
GridFieldDetailForm(for exampleGridFieldConfig_Base), or - the copy is not in the GridField's list.
duplicate()does not add the copy to amany_manylist, or to a list filtered on something the copy does not match; ahas_manylist keeps it, because the copy keeps the parent's ID.
This replaces redirecting from the model's onAfterDuplicate(), which fires on every
duplicate() call, not only on a click of this button.
The module's only configuration adds its stylesheet to the CMS:
SilverStripe\Admin\LeftAndMain:
extra_requirements_css:
- "restruct/silverstripe-copybutton:css/GridFieldCopyButton.css"Labels are translatable under GridAction.Copy, GridAction.COPY_DESCRIPTION and
GridFieldAction_Copy.CreatePermissionsFailure (see lang/).
The module cannot be tested on its own: it needs a host Silverstripe project. Require it there
through a Composer path repository with symlink: true - /tests is export-ignore, so a dist
or mirrored install contains no tests - add "Unisolutions\\Tests\\": "vendor/restruct/silverstripe-copybutton/tests/" to the host's autoload-dev, then:
# Silverstripe 5 (PHPUnit 9) - the path must come before flush=1
vendor/bin/phpunit vendor/restruct/silverstripe-copybutton/tests flush=1
# Silverstripe 6 (PHPUnit 11) - a flush=1 argument is ignored, use the env var
SS_PHPUNIT_FLUSH=1 vendor/bin/phpunit vendor/restruct/silverstripe-copybutton/testsCI runs the same suite against Silverstripe 5 and 6 on pushes to main and on pull requests; see .github/workflows/ci.yml.
This package replaces dhensby/silverstripe-copybutton (and unisolutions/silverstripe-copybutton):
all three ship the same class, so only one of them can be installed. Upstream publishes its own
Silverstripe 6-only 3.0.0; this package's 3.x covers Silverstripe 5 and 6 and differs in behaviour
(see CHANGELOG.md).
BSD-3-Clause, see LICENSE.