Search by

restruct / silverstripe-copybutton

micschk

Adds copy/duplicate button to the GridField.

Package info

github.com/restruct/silverstripe-copybutton

Type:silverstripe-vendormodule

pkg:composer/restruct/silverstripe-copybutton

Fund package maintenance!

restruct

Statistics

Installs: 3 388

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 1

3.1.1 2026-10-08 11:41 UTC

README

Adds a copy/duplicate button to GridFields

Maintained by Restruct. If this module saves you time, you can support ongoing maintenance.

Original author

Elvinas Liutkevičius <elvinas (at) unisolutions (dot) eu> (Forked from dhensby's SS4-updated fork for quick maintenance & updates/tags)

Requirements

  • Silverstripe 5 or 6 (silverstripe/framework)
  • PHP 8.1 or newer (Silverstripe 6 itself needs 8.3)

Installation

composer require restruct/silverstripe-copybutton

Version compatibility

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.

Usage

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;
    }
}

What a copy does

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's cascade_duplicates config; 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 a ValidationException ("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();
    }
}

Open the copy after copying

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 example GridFieldConfig_Base), or
  • the copy is not in the GridField's list. duplicate() does not add the copy to a many_many list, or to a list filtered on something the copy does not match; a has_many list 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.

Configuration

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/).

Running the tests

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/tests

CI runs the same suite against Silverstripe 5 and 6 on pushes to main and on pull requests; see .github/workflows/ci.yml.

Relation to dhensby/silverstripe-copybutton

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).

Licence

BSD-3-Clause, see LICENSE.