# Filters and hooks - Packboard 1.x documentation

> Add board columns, card labels, a step to Mark shipped, printable documents and screens, from PHP and JavaScript.

Packboard's hooks are WordPress filters and actions, plus a few JavaScript filters on the board and grid (through `wp.hooks`). Put PHP examples in a small custom plugin or your child theme's `functions.php`.

## packboard_board_lanes

Adds a column before **To pick** for orders that wait outside the flow. Packboard doesn't know what the column is: your callbacks decide which orders it holds and what its one action does. Dragging a card from it into To pick runs the action too.

```php
add_filter( 'packboard_board_lanes', function ( $lanes ) {
	$lanes['awaiting-stock'] = array(
		'title'       => 'Awaiting stock',
		'description' => 'On hold until the supplier delivers.',             // optional
		'orders'      => fn() => wc_get_orders( array( 'status' => 'on-hold', 'limit' => -1 ) ),
		'note'        => fn( WC_Order $order ) => 'Since ' . $order->get_date_modified()->date_i18n( 'M j' ), // optional
		'action'      => 'Ready to pick',
		'run'         => function ( WC_Order $order ) {
			$order->update_status( 'processing' );
			return true;                                                      // true when the order moved on
		},
	);
	return $lanes;
} );
```

A lane without a title, an action, or `orders` and `run` callbacks is ignored.

## packboard_card_tags

Short labels on an order's card. Gets the list so far and the order; return strings (empty ones are dropped).

```php
add_filter( 'packboard_card_tags', function ( $tags, $order ) {
	if ( $order->get_meta( '_gift_wrap' ) ) {
		$tags[] = 'Gift wrap';
	}
	return $tags;
}, 10, 2 );
```

## packboard_card_sort

Where a card goes in its column. Return a sort key: cards with a key come first, ordered by key as text, then cards without one, oldest first.

```php
// Express shipping first.
add_filter( 'packboard_card_sort', function ( $key, $order ) {
	return $order->has_shipping_method( 'flat_rate:2' ) ? '0-express' : $key;
}, 10, 2 );
```

## packboard_ship_order

An action that runs for each order **Mark shipped** is about to complete, before its status changes. Whatever you save on the order here is saved with it, and is already there when WooCommerce sends the "Completed order" email. Gets the order and the REST request, which carries any fields the ship dialog added (see `packboard.shipDialog` below).

Throw an exception to stop one order from shipping: it stays in Packed and the board reports it.

```php
add_action( 'packboard_ship_order', function ( $order, $request ) {
	$order->update_meta_data( '_shipped_by', wp_get_current_user()->display_name );
}, 10, 2 );
```

Packboard Pro uses this action to record carriers and tracking numbers.

## packboard_print_documents

Adds a printable document. It's served on Packboard's print page, which has a Print button and Packboard's print stylesheet, at the print URL with `&doc=<key>&ids=<order IDs>`. `styles` are registered style handles printed after Packboard's.

```php
add_filter( 'packboard_print_documents', function ( $documents ) {
	$documents['labels'] = array(
		'title'  => 'Address labels',
		'render' => function ( array $orders ) {
			foreach ( $orders as $order ) {
				echo '<div class="label">' . wp_kses_post( $order->get_formatted_shipping_address() ) . '</div>';
			}
		},
	);
	return $documents;
} );
```

Open it from the board or grid with `window.packboardDom.openPrint( 'labels', ids )`.

## packboard_admin_pages and packboard_enqueue_page

`packboard_admin_pages` adds a screen under the WooCommerce menu (slug => title), next to Order Grid and Fulfillment. It renders an empty `#packboard-root` and loads Packboard's admin stylesheet and the `packboard-dom` script, which sets `window.packboardDom` (`el()` to build elements, `openPrint()`) and `window.packboardConfig` (order statuses, stage names, the order edit and print URLs).

`packboard_enqueue_page` fires on every Packboard screen with its slug, before the screen's own script, so a script you enqueue there can add `wp.hooks` filters before the board or grid first draws.

```php
add_action( 'packboard_enqueue_page', function ( $slug ) {
	if ( 'packboard-fulfillment' === $slug ) {
		wp_enqueue_script( 'my-board', plugins_url( 'board.js', __FILE__ ), array( 'packboard-dom', 'wp-hooks', 'wp-i18n' ), '1.0', true );
	}
} );
```

## JavaScript filters

All four use `wp.hooks.addFilter( name, namespace, callback )`.

**packboard.boardActions** `( actions, columnKey )`: the buttons over a stage column (`to_pick`, `picking`, `packed`). Each is `{ id, label, primary, run( ids ) }`; `run` gets the selected order IDs. Packboard's ids are `start-picking`, `packing-slips`, `mark-packed` and `mark-shipped`.

```js
wp.hooks.addFilter( 'packboard.boardActions', 'my-plugin', ( actions, column ) =>
	'packed' === column
		? [ { id: 'labels', label: 'Address labels', run: ( ids ) => window.packboardDom.openPrint( 'labels', ids ) }, ...actions ]
		: actions
);
```

**packboard.cardLinks** `( nodes, order, columnKey )`: elements added at the bottom of a card.

**packboard.shipDialog** `( step, orders )`: what the Mark shipped dialog asks for. `step` is `{ fields, cell, data, description }`: `fields` are elements above the order list, `cell( order )` returns an element for that order's row (or `null`), `data()` returns extra fields to send, which reach `packboard_ship_order` as request parameters (they can't replace `ids` or `stage`), and `description` is the text under the list. Return a new object built on the one you get, so other filters still apply. Pressing Enter in a text field never submits the dialog, so barcode scanners are safe.

**packboard.gridActions** `( actions )`: buttons for the selected rows on the order grid, each `{ id, label, run( ids ) }`.

## packboard_loaded

An action that fires once Packboard has loaded (on `plugins_loaded`, and only when WooCommerce is active). Hook an add-on's setup to it, so it only runs when Packboard is there. `PACKBOARD_VERSION` is defined from then on.

## REST API

Everything the screens do goes through `packboard/v1`, for users who can edit orders:

| Route | Does |
|---|---|
| `GET /orders` | The grid: `page`, `per_page` (up to 200), `status`, `search` |
| `POST /orders/bulk` | `ids` and `status`: change the status of up to 500 orders |
| `GET /fulfillment` | The board: `columns` (`to_pick`, `picking`, `packed`), `lanes` and `truncated` |
| `POST /fulfillment/stage` | `ids` and `stage` (`to_pick`, `picking`, `packed`, `shipped`), plus any ship dialog fields |
| `POST /fulfillment/lane` | `ids` and `lane`: run a lane's action |

Each answers with the orders it changed (`updated`) and the IDs it couldn't (`failed`).

Source: https://www.extendiment.com/plugins/packboard/docs/1.x/filters-and-hooks
