How to remove unwanted columns from Express form CSV export?

I’m working on a project where we’re doing a lot of exporting of (Express) form data as CSVs, and one thing I’ve noticed is that there’s a few hard-coded fields in this export.

  • publicIdentifier
  • dateCreated
  • dateModified
  • site
  • authorName

Whilst I can see how these fields would be handy, my client has asked if they can be removed.
The fields appear to be hard coded into the getHeaders function, deep within the CsvWriter used for Express:

I just thought I’d check in case I’ve overlooked something - does anyone know a neat way/trick to remove those, without having to write my own new CSV export function?

I doubt you can do anything without overriding class.
Removing yields at the beginning should do the trick.

I would say, if you want to do it sitewide, the “cleanest” method would be registering custom service provider and overriding core CsvWriter class.
Unfortunately, since this is a private method, you have to copy whole class and not just extend core class and modify one method.

I guess you can also just override dashboard controller and swap CsvWriter with you custom class.

I appreciate the thoughts @Parasek

That’s kind of the same conclusions I came too. A lot of work to just remove a few columns I reckon.

I might be just as easy to submit a PR to the core to add an option to exclude those fields in the export, through some config value!

An alternative approach, maybe it could be handled by creating a new end point that internally calls the old end point, gets the CSV, removes the unwanted columns, and passes it through.

Still non-trivial, but maybe easier than messing with the core classes.

@JohntheFish You’ve described what I was thinking of doing instead, as reading and re-exporting a CSV isn’t too tricky. I really just wanted the links within the dashboard to work as is.

It really comes down to how insistant my client is really!

alternatively, if you can modify the single page’s controller (easy to do) you can add a formatter to the writer using addFormatter()
A formatter is any callable that will receive an array of the data so that might be the best place to do that.

Here’s what I came up with, something dropped in /application/controllerss/single_page/dashboard/reports/forms.php

It feels very hacky, but at least it only impacts one function.

(updated with Nour’s improvements)

namespace Application\Controller\SinglePage\Dashboard\Reports;

use Concrete\Core\Controller\Traits\DashboardExpressEntryDetailsTrait;
use Concrete\Core\Controller\Traits\DashboardSelectableExpressEntryListTrait;
use Concrete\Core\Csv\WriterFactory;
use Concrete\Core\Entity\Express\Entity;
use Concrete\Core\Express\Export\EntryList\CsvWriter;
use Concrete\Core\Localization\Service\Date;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\StreamedResponse;

class Forms extends \Concrete\Controller\SinglePage\Dashboard\Reports
    use DashboardSelectableExpressEntryListTrait;
    use DashboardExpressEntryDetailsTrait;

    protected function exportCsv(Entity $entity, $searchMethod = null)
        $permissions = new \Permissions($entity);
        if (!$permissions->canViewExpressEntries()) {
            throw new \Exception(t('Access Denied'));

        $headers = [
            'Content-Type' => 'text/csv',
            'Content-Disposition' => 'attachment; filename=' . $entity->getHandle() . '.csv',

        $config = $this->app->make('config');
        $bom = $config->get('concrete.export.csv.include_bom') ? $config->get('concrete.charset_bom') : '';
        $datetime_format_constant = $config->get('concrete.export.csv.datetime_format');
        if (!defined($datetime_format_constant)) {
            $datetime_format_constant = sprintf('DATE_%s', $datetime_format_constant);
        if (defined($datetime_format_constant)) {
            $datetime_format = constant($datetime_format_constant);
        } else {
            $datetime_format = DATE_ATOM;
        if ($searchMethod == 'advanced_search') {
            $query = $this->getQueryFactory()->createFromAdvancedSearchRequest(
        } else {
            $query = $this->createDefaultQuery($entity);
        $result = $this->createSearchResult($entity, $query);
        $entryList = $result->getItemListObject();

        return new StreamedResponse(function () use ($entity, $entryList, $bom, $datetime_format) {
            $writer = $this->app->make(CsvWriter::class, [
                'writer' => $this->app->make(WriterFactory::class)->createFromPath('php://output', 'w')
                    ->addFormatter(function ($record) {
                        return $record;
                'dateFormatter' => new Date(),
                'datetime_format' => $datetime_format
            echo $bom;
        }, 200, $headers);
1 Like