# Adding columns - Sheetline 1.x documentation

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

Any post field can become a Bulk Editor column with the `sheetline_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 Sheetline Pro bulk changes, undo and CSV export and import.

## A first column

```php
// A "Subtitle" text column for posts, stored in post meta.
add_filter( 'sheetline_columns', function ( $columns, $post_type ) {
	if ( 'post' !== $post_type ) {
		return $columns;
	}
	$columns['subtitle'] = array(
		'label' => 'Subtitle',
		'group' => 'Extras',
		'type'  => 'text',
		'get'   => function ( WP_Post $post ) {
			return (string) get_post_meta( $post->ID, 'subtitle', true );
		},
		'set'   => function ( \Extendiment\Sheetline\Draft $draft, $value ) {
			$draft->meta( 'subtitle', sanitize_text_field( $value ) );
		},
		'sort'  => array( 'meta' => 'subtitle' ),
	);
	return $columns;
}, 10, 2 );
```

The key (`subtitle`) must be unique. The filter runs once per post type, with the post type as its second argument, so each Bulk Editor can have its own columns.

## Setters record, they don't save

A column's `set` gets a **Draft**: the post's pending changes. Record the new value there and Sheetline writes everything once, after checking every cell of the post:

| Draft method | What it does when the post is saved |
|---|---|
| `$draft->field( 'post_title', $value )` | Sets a `wp_update_post()` field (unslashed) |
| `$draft->meta( $key, $value )` | Updates post meta (`''` or `null` deletes the key) |
| `$draft->terms( $taxonomy, $ids )` | Replaces the post's terms of a taxonomy |
| `$draft->before( fn( $id ) => … )` | Runs before the post is updated |
| `$draft->after( fn( $id ) => … )` | Runs after the post is updated |

`$draft->post` is the post as loaded, and `$draft->get( 'post_status' )` a field as it will be saved. Never write to the database in `set` yourself: Sheetline Pro calls the same setters to preview bulk changes and imports without saving.

## 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 ( WP_Post $post )` returning the value to edit |
| `set` | yes | `function ( Draft $draft, $value )` recording a new value |
| `group` | no | Groups related columns (default "Other") |
| `options` | for `select` and `terms` | `value => label` pairs, or a function returning them (called only when needed) |
| `applies` | no | `function ( WP_Post $post )` returning false where the field doesn't exist (the cell shows a dash) |
| `can` | no | `function ( WP_Post $post )` returning false where the current user may not change it (the cell is read-only). Sheetline already checks `edit_post` for every post |
| `hidden` | no | `true` to hide the column until switched on in Screen Options |
| `sort` | no | Makes it sortable: a `WP_Query` orderby value (e.g. `'title'`), or `array( 'meta' => 'my_key', 'num' => true )` |
| `bulk` | no | `false` keeps the column out of Sheetline Pro's bulk changes |

## Types

| Type | Value | Edited with |
|---|---|---|
| `text` | string | a text box |
| `longtext` | HTML string | a larger text box over the cell |
| `number` | number as a string | a text box |
| `date` | `Y-m-d` string, `''` for none | a date picker |
| `datetime` | `Y-m-d H:i` string | a date and time 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`. Sanitize them as you would any input.

## Refusing a value

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

```php
'set' => function ( \Extendiment\Sheetline\Draft $draft, $value ) {
	$value = trim( (string) $value );
	if ( '' !== $value && ! preg_match( '/^\d{4}$/', $value ) ) {
		throw \Extendiment\Sheetline\Columns::invalid( 'Enter a year, such as 2026.' );
	}
	$draft->meta( 'year', $value );
},
```

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

## Helpers

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

- `Columns::option( $value, $options )`: one of the options' keys; refuses anything else.
- `Columns::bool( $value )`: `true` for "yes", "1", "true" or "on".
- `Columns::integer( $value )`, `Columns::number( $value )`: a whole number, or any number (a comma works as the decimal point).
- `Columns::date( $value )`, `Columns::datetime( $value )`: a valid date, or date and time, in the formats above.
- `Columns::term_options( $taxonomy )`: a taxonomy's terms as `ID => 'Parent › Child'`, ready for `options`.
- `Columns::term_ids( $value, $taxonomy )`: the IDs, each an existing term of the taxonomy.
- `Columns::statuses()`, `Columns::authors( $post_type )`: the status and author options the grid uses.

```php
// A taxonomy as a terms column, for a "book" post type.
add_filter( 'sheetline_columns', function ( $columns, $post_type ) {
	if ( 'book' === $post_type ) {
		$columns['series'] = array(
			'label'   => 'Series',
			'type'    => 'terms',
			'options' => fn() => \Extendiment\Sheetline\Columns::term_options( 'series' ),
			'can'     => fn() => current_user_can( get_taxonomy( 'series' )->cap->assign_terms ),
			'get'     => fn( $p ) => wp_get_object_terms( $p->ID, 'series', array( 'fields' => 'ids' ) ),
			'set'     => fn( $d, $v ) => $d->terms( 'series', \Extendiment\Sheetline\Columns::term_ids( $v, 'series' ) ),
		);
	}
	return $columns;
}, 10, 2 );
```

Taxonomies registered for a post type with `show_ui` already get a column of their own, so you only need this for something special.

## 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( 'sheetline_columns', function ( $columns ) {
	$at  = array_search( 'title', array_keys( $columns ), true ) + 1;
	$new = array( 'kicker' => array( /* … */ ) );
	return array_slice( $columns, 0, $at, true ) + $new + array_slice( $columns, $at, null, true );
} );
```

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

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