# Adding columns - Gridline 1.x documentation

> Add your own fields to the Bulk Editor with the gridline_columns filter: the column format, value types, validation and sorting.

Any product field can become a Bulk Editor column with the `gridline_columns` filter. Put the code in a small custom plugin or your child theme's `functions.php`. Columns you add get everything the built-in ones have: keyboard editing, highlighted unsaved changes, errors on their cell, Screen Options, and with Gridline Pro bulk changes, undo and CSV export.

## A first column

```php
// A "Gift wrap available" yes/no column, stored in product meta.
add_filter( 'gridline_columns', function ( $columns ) {
	$columns['gift_wrap'] = array(
		'label' => 'Gift wrap',
		'group' => 'Extras',
		'type'  => 'bool',
		'get'   => function ( WC_Product $product ) {
			return $product->get_meta( '_gift_wrap' ) ? 'yes' : 'no';
		},
		'set'   => function ( WC_Product $product, $value ) {
			$product->update_meta_data( '_gift_wrap', 'yes' === $value ? 1 : '' );
		},
	);
	return $columns;
} );
```

The key (`gift_wrap`) must be unique. The product is saved by Gridline after `set` runs, so don't call `save()` yourself.

## The column format

| Key | Required | What it is |
|---|---|---|
| `label` | yes | Shown in the header and in Screen Options |
| `type` | yes | How the value is shown and edited (see below) |
| `get` | yes | `function ( WC_Product $product )` returning the value to edit |
| `set` | yes | `function ( WC_Product $product, $value )` applying a new value |
| `group` | no | Groups related columns (default "Other") |
| `options` | for `select` and `terms` | `value => label` pairs |
| `applies` | no | `function ( WC_Product $product )` returning false where the field doesn't exist (the cell is greyed out) |
| `show` | no | `function ( WC_Product $product )` returning true where the field can't be edited (`applies` is false) but its value should still be shown, greyed out, e.g. a value worked out from other fields |
| `hidden` | no | `true` to hide the column until switched on in Screen Options |
| `sort` | no | Makes it sortable: an `orderby` value for `wc_get_products()` (e.g. `'title'`), or `array( 'meta' => '_my_key', 'num' => true )` |

## Types

| Type | Value | Edited with |
|---|---|---|
| `text` | string | a text box |
| `longtext` | HTML string | a larger text box over the cell |
| `price` | decimal string, `''` for none | a text box; shown in your currency |
| `number` | number as a string | a text box |
| `date` | `Y-m-d` string, `''` for none | a date picker |
| `select` | one of the `options` keys | a dropdown |
| `bool` | `'yes'` or `'no'` | a Yes/No dropdown |
| `terms` | array of term IDs | a checkbox list of the `options` |
| `image` | attachment ID as a string, `''` for none | the media library |
| `images` | array of attachment IDs | the media library, several in order |
| `readonly` | string | shown, not editable |

Values arrive in `set` as the grid sends them: strings, or arrays of IDs for `terms` and `images`. Always write them through the product's own setters or meta functions, so WooCommerce and other plugins see the change.

## Refusing a value

Throw an exception with a message for the shop manager. It appears under the cell in red, and that product isn't saved:

```php
'set' => function ( WC_Product $product, $value ) {
	$value = trim( (string) $value );
	if ( '' !== $value && ! preg_match( '/^\d{8,14}$/', $value ) ) {
		throw \Extendiment\Gridline\Columns::invalid( 'A GTIN is 8 to 14 digits.' );
	}
	$product->update_meta_data( '_gtin', $value );
},
```

`Columns::invalid()` returns an `InvalidArgumentException`; any `InvalidArgumentException` works.

## Helpers

`Extendiment\Gridline\Columns` has the helpers the built-in columns use:

- `Columns::decimal( $value )`: a number of 0 or more (or `''`), as WooCommerce stores it; refuses anything else.
- `Columns::option( $value, $options )`: one of the options' keys; refuses anything else.
- `Columns::bool( $value )`: `true` for "yes", "1", "true" or "on".
- `Columns::term_options( $taxonomy )`: a taxonomy's terms as `ID => 'Parent › Child'`, ready for `options`.
- `Columns::term_ids( $value, $options )`: the term IDs, each one of the options.

```php
// A brand taxonomy as a terms column.
add_filter( 'gridline_columns', function ( $columns ) {
	$brands = \Extendiment\Gridline\Columns::term_options( 'product_brand' );
	$columns['brands'] = array(
		'label'   => 'Brands',
		'type'    => 'terms',
		'options' => $brands,
		'applies' => fn( $p ) => ! $p->is_type( 'variation' ),
		'get'     => fn( $p ) => wp_get_post_terms( $p->get_id(), 'product_brand', array( 'fields' => 'ids' ) ),
		'set'     => fn( $p, $v ) => wp_set_post_terms( $p->get_id(), \Extendiment\Gridline\Columns::term_ids( $v, $brands ), 'product_brand' ),
	);
	return $columns;
} );
```

## Where the column goes

Columns appear in the array's order. Adding a key puts the column at the end; to place it elsewhere, rebuild the array:

```php
add_filter( 'gridline_columns', function ( $columns ) {
	$at  = array_search( 'sku', array_keys( $columns ), true ) + 1;
	$new = array( 'gtin' => array( /* … */ ) );
	return array_slice( $columns, 0, $at, true ) + $new + array_slice( $columns, $at, null, true );
} );
```

Gridline Pro adds its columns at priority 5, so columns added at the default priority 10 come after them.

## Variable products and variations

Use `applies` to grey out cells where a field doesn't exist, for example `fn( $p ) => ! $p->is_type( 'variable' )` for a field only simple products and variations have. With Gridline Pro, variations get rows of their own, so decide whether your field applies to them too.

Source: https://www.extendiment.com/plugins/gridline/docs/1.x/adding-columns
