# Getting started

HivePress is an open-source solution for building any type of directory and listing websites, such as business directories, job boards, classifieds, marketplaces, and whatnot.

While it's implemented as a [WordPress plugin](https://wordpress.org/plugins/hivepress/), it has its own lightweight MVC framework that acts as a wrapper for the WordPress API. This makes extending the core functionality and working with templates, forms, queries, etc. much easier.

The framework docs are organized to resemble the HivePress file structure, so you can easily navigate the [code reference](https://hivepress.github.io/code-reference/) and check the implementation while reading the docs.

These docs assume that you are already familiar with the [WordPress API](https://developer.wordpress.org/) and concepts, such as post types, taxonomies, meta boxes, actions, filters, and so on.

You can [start with a tutorial](/developer-docs/tutorials/create-a-custom-hivepress-extension), explore the framework along with the code and hook references, or browse the collection of [code snippets](https://gist.github.com/search?q=user%3Ahivepress) for HivePress.

Have fun and build something awesome!


# Tutorials


# Integrate your theme with HivePress

In this tutorial, we’ll show how to integrate a third-party theme with HivePress.

By default, HivePress should work fine with any theme, but if you want HivePress layouts to match your theme design and ensure 100% compatibility, then a few changes are required.

### Declare HivePress support

If you activate a third-party theme with HivePress, it will show a notice that warns the site administrator about the possible incompatibility issues. To hide this notice and declare HivePress support explicitly, add this code to the theme’s `functions.php` file:

```php
add_action(
	'after_setup_theme',
	function() {
		add_theme_support( 'hivepress' );
	}
);
```

### Add header menu

HivePress has a few links and buttons that should be displayed in the site header, e.g. the login link and the **Add Listing** button. To display them, add the following code somewhere in the `header.php` theme file:

```php
if ( function_exists( 'hivepress' ) ) {
	echo hivepress()->template->render_menu();
}
```

We recommend adding it next to the main menu. Then, check if these links and buttons are properly aligned, and also make sure that they look good on mobile.

### Add page wrapper

The most common issue that may occur with a third-party theme is related to HivePress page width and alignment. If the HivePress page content is not wrapped properly, you can add a custom HTML wrapper using this code in the theme’s `functions.php` file:

```php
add_filter(
	'hivepress/v1/blocks/page',
	function( $args ) {
		return hivepress()->helper->merge_arrays(
			$args,
			[
				'blocks' => [
					'before' => [
						'type'    => 'content',
						'content' => '<div class="my-custom-wrapper">',
						'_order'  => 1,
					],

					'after'  => [
						'type'    => 'content',
						'content' => '</div>',
						'_order'  => 1000,
					],
				],
			]
		);
	}
);
```

Alternatively, you can add a custom CSS class to the existing wrapper with this code:

```php
add_filter(
	'hivepress/v1/blocks/page',
	function( $args ) {
		return hivepress()->helper->merge_arrays(
			$args,
			[
				'attributes' => [
					'class' => [ 'my-custom-wrapper' ],
				],
			]
		);
	}
);
```

Also, you can use CSS to adjust the HivePress page width and padding. For example, add this code to the theme’s `style.css` file and change the sample values:

```css
.hp-page {
	max-width: 1234px;
	padding: 32px;
}
```

### Customize styles

By default, HivePress styles are pretty basic, so it may be a good idea to style its layouts and elements to match your theme design better.

All CSS classes in HivePress have the `hp-` prefix to avoid conflicts with other theme and plugin styles. We use [BEM](https://css-tricks.com/bem-101/) notation for naming CSS classes, so they are very intuitive, e.g. the listing CSS class is `.hp-listing`, its title class is `.hp-listing__title`, its **Price** attribute class is `.hp-listing__attribute--price` and so on.

You can easily inspect HTML elements via the browser [development tools](https://developer.chrome.com/docs/devtools/open/#elements) to check their CSS classes and then add custom styles to the theme’s `style.css` file accordingly. We also recommend enabling all the available HivePress extensions (e.g. Favorites, Messages, Geolocation, Reviews) and style their layouts if required.

### Customize templates

Customizing HivePress styles may be enough, but if the advanced layout changes are required for your theme, then you probably need to customize the HivePress templates.

#### Using template hooks

In HivePress, [templates](/developer-docs/framework/templates) are defined as arrays of [blocks](/developer-docs/framework/blocks). With blocks, it’s easy to re-use and customize specific layout parts without affecting the whole template.

You can customize HivePress templates by adding, changing or deleting blocks via the `hivepress/v1/templates/{template_name}` filter hook. Template names always follow the `{entity}_{context}_{layout}` pattern, so it’s really easy to find the related [templates](https://hivepress.github.io/code-reference/namespaces/hivepress-templates.html).

For example, the code snippet below changes the number of columns on the **Listings** page by overriding the `columns` parameter of the `listings` block in the `listings_view_page` template:

```php
add_filter(
	'hivepress/v1/templates/listings_view_page',
	function( $template ) {
		return hivepress()->helper->merge_trees(
			$template,
			[
				'blocks' => [
					'listings' => [
						'columns' => 3,
					],
				],
			]
		);
	}
);
```

And this code snippet changes the description position on the listing page by overriding the `_order` parameter of the `listing_description` block in the `listing_view_page` template:

```php
add_filter(
	'hivepress/v1/templates/listing_view_page',
	function( $template ) {
		return hivepress()->helper->merge_trees(
			$template,
			[
				'blocks' => [
					'listing_description' => [
						'_order' => 123,
					],
				],
			]
		);
	},
	1000
);
```

You can check more examples of customizing templates in our [code snippet collection](https://gist.github.com/search?q=user%3Ahivepress+hivepress%2Fv1%2Ftemplates\&ref=searchresults).

#### Using template overrides

While templates consist of blocks, there’s a block type that loads specific HTML files from the `templates` subdirectory of the HivePress plugin, referred to as "template parts". You can override any template parts by copying them to the theme directory.

{% hint style="info" %}
It's good practice to avoid overriding template parts where possible, because they may be updated in HivePress, while the theme will still load the outdated copy.
{% endhint %}

First, create a `hivepress` subdirectory in your theme directory. Then, find the template part that you want to customize in the `templates` subdirectory of the HivePress plugin or any of its extensions, and copy this file to the theme’s `hivepress` subdirectory you’ve just created, preserving the relative file path.

File paths always follow the `{entity}/{context}/{layout}` pattern, so it’s easy to find the related template parts in the [templates](https://github.com/hivepress/hivepress/tree/master/templates) subdirectory of HivePress or its extensions.

Once you copy the template part, HivePress will load it instead of the default one, so you can customize its HTML content without changing the plugin files directly.

For example, if you want to customize the listing description, copy the `listing-description.php` file from the `templates/listing/view/page` HivePress subdirectory to the theme’s `hivepress/listing/view/page` subdirectory. Then, change its HTML content and check if the changes are reflected on the listing page.

### Keep developing

Congratulations! You’ve just integrated your theme with HivePress. Even though there's a lot more to the HivePress development than what you’ve seen so far, you’re now familiar with integrating or even creating custom themes for HivePress.

If you have any questions about HivePress development, please check the available docs and feel free to join the HivePress [developer community](https://community.hivepress.io/).


# Create a custom HivePress extension

In this tutorial, we'll create a custom HivePress extension that allows users to:

* Follow or unfollow any vendor
* View listings from the followed vendors on a single page
* Get emails about new listings from the followed vendors
* Unfollow all vendors with one click

While this extension is pretty simple, it covers the main aspects of the HivePress framework, so by the end of this tutorial, you should be able to create your own extension for HivePress.

Before we begin, please make sure that you have a working WordPress installation and at least basic WordPress development skills. The result of this tutorial is available on [GitHub](https://github.com/hivepress/foo-followers), so you can browse the complete source code or download the extension to test it locally.

### Create the main file

First, create a directory in the `wp-content/plugins` WordPress subdirectory. The directory name will be used as the extension identifier, so make sure it's unique enough to avoid conflicts with other HivePress extensions (use lowercase letters, numbers, and hyphens only).

For this tutorial, we'll name it "foo-followers", where "foo" is a unique prefix (e.g. your company name or abbreviation), and the "followers" part describes the extension purpose.

Next, create a PHP file with the same name inside the extension directory. This is the main file that is loaded by WordPress automatically when the extension is active.

```php
<?php
/**
 * Plugin Name: Followers for HivePress
 * Description: Allow users to follow vendors.
 * Version: 1.0.0
 * Author: Foo
 * Author URI: https://example.com/
 * Text Domain: foo-followers
 * Domain Path: /languages/
 */

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

// Register extension directory.
add_filter(
	'hivepress/v1/extensions',
	function( $extensions ) {
		$extensions[] = __DIR__;

		return $extensions;
	}
);
```

As you can see, this file contains the extension details, such as:

* **Plugin Name & Description** - the extension name and short description of its purpose;
* **Author & Author URI** - your or your company name with an optional website URL;
* **Text Domain** - used for translating the extension, matches the main file name.

{% hint style="info" %}
If you plan to distribute your extension, please name it "Something for HivePress" rather than "HivePress Something" to avoid trademark violation.
{% endhint %}

Also, there’s a simple code that prevents direct file access by URL so that it can be loaded by WordPress only. We’ll add the same code to every file created in this tutorial:

```php
defined( 'ABSPATH' ) || exit;
```

The only thing the main file does is register the extension directory via the `hivepress/v1/extensions` hook to allow HivePress to detect and load all the other extension files automatically:

```php
add_filter(
	'hivepress/v1/extensions',
	function( $extensions ) {
		$extensions[] = __DIR__;

		return $extensions;
	}
);
```

{% hint style="info" %}
If you plan to submit your extension to the [WordPress.org](https://wordpress.org/plugins/) repository, make sure to follow [its guidelines](https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/) and add the `readme.txt` along with the license file.
{% endhint %}

After you create the main file, go to **WordPress > Plugins** and activate the extension:

![](/files/28sbgVetqDhcDHifxZMc)

### Create a component

In HivePress, [components](/developer-docs/framework/components) are PHP classes used to group actions, filters, and helper functions. Let’s create an empty component and add the extension-specific functions to it later.

Create a `class-followers.php` file in the `includes/components` extension subdirectory. Notice that it has the `class-` prefix, and its name matches the extension name. It must be unique enough to avoid conflicts with other HivePress components (use lowercase letters, numbers, and hyphens only).

The PHP class name must be based on the file name, but with underscores instead of hyphens and no lowercase restriction (e.g. `Foo_Bar` class for the `class-foo-bar.php` file).

```php
<?php
namespace HivePress\Components;

use HivePress\Helpers as hp;
use HivePress\Models;
use HivePress\Emails;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Component class.
 */
final class Followers extends Component {

	/**
	 * Class constructor.
	 *
	 * @param array $args Component arguments.
	 */
	public function __construct( $args = [] ) {
		// Attach functions to hooks here (e.g. add_action, add_filter).

		parent::__construct( $args );
	}

	// Implement the attached functions here.
}
```

{% hint style="info" %}
If your extension is simple enough, you can fully implement it within a component and skip other steps, but we recommend using the HivePress framework where possible.
{% endhint %}

### Create a model

In HivePress, [models](/developer-docs/framework/models) are PHP classes that represent the WordPress database entities such as posts, comments, or terms. Using models makes working with the database much easier.

Let’s create a `Follow` model for storing the follower ID along with the followed vendor ID. Create a `class-follow.php` file in the `includes/models` extension subdirectory. The file and PHP class naming conventions are the same as for components.

```php
<?php
namespace HivePress\Models;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Model class.
 */
class Follow extends Comment {

	/**
	 * Class constructor.
	 *
	 * @param array $args Model arguments.
	 */
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				'fields' => [
					'user'   => [
						'type'     => 'id',
						'required' => true,
						'_alias'   => 'user_id',
						'_model'   => 'user',
					],

					'vendor' => [
						'type'     => 'id',
						'required' => true,
						'_alias'   => 'comment_post_ID',
						'_model'   => 'vendor',
					],
				],
			],
			$args
		);

		parent::__construct( $args );
	}
}
```

Notice that the model class is based on the `Comment` class. This means that the model objects will be stored as WordPress comments of a custom type. We implement the model this way because comments are linked to both users and posts in the WordPress [database schema](https://codex.wordpress.org/Database_Description). Since vendors are implemented as posts of a custom type, we can use this model to store both the follower (user) and the followed vendor IDs.

The `Follow` model contains 2 fields:

* **user** - mapped to the `user_id` database field, used for storing the follower ID;
* **vendor** - mapped to the `comment_post_ID` database field, used for storing the followed vendor ID.

Also, if the model is based on the `Comment` class, WordPress will show the model objects in the comment feeds. To keep the model objects hidden, create a `comment-types.php` file in the `includes/configs` extension subdirectory:

```php
<?php
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

return [
	'follow' => [
		'public' => false,
	],
];
```

The code above contains a configuration that makes the `follow` comment type registered by the model we created private.

### Create a template

In HivePress, [templates](/developer-docs/framework/templates) are defined as PHP classes that contain arrays of [blocks](/developer-docs/framework/blocks). With blocks, it’s easy to re-use and customize specific layout parts without affecting the whole template.

Let’s create a template for a page that displays all listings from the followed vendors. Create a `class-listings-feed-page.php` file in the `includes/templates` extension subdirectory. The file and PHP class naming conventions are the same as for components.

{% hint style="info" %}
It’s good practice to follow the `{entity}-{context}-{layout}` pattern for naming templates (e.g. `Listing_Edit_Page`, `Vendor_View_Block`).
{% endhint %}

```php
<?php
namespace HivePress\Templates;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Template class.
 */
class Listings_Feed_Page extends User_Account_Page {

	/**
	 * Class constructor.
	 *
	 * @param array $args Template arguments.
	 */
	public function __construct( $args = [] ) {
		$args = hp\merge_trees(
			[
				'blocks' => [
					'page_content' => [
						'blocks' => [
							'listings'               => [
								'type'    => 'listings',
								'columns' => 2,
								'_order'  => 10,
							],

							'listing_pagination'     => [
								'type'   => 'part',
								'path'   => 'page/pagination',
								'_order' => 20,
							],
						],
					],
				],
			],
			$args
		);

		parent::__construct( $args );
	}
}
```

As you can see, the template class is based on the `User_Account_Page` class. This means that the template inherits the user account page layout and adds custom blocks to it.

The template we created adds 2 blocks to the `page_content` area:

* **listings** - displays listings for the current page;
* **listing\_pagination** - displays the page links for navigation.

The block names must be unique within the template scope. Each block is defined as an array containing the block type and extra parameters used to render the block.

### Create a controller

Now, we need to define custom URLs that will render the template we created and perform specific actions, such as following or unfollowing a vendor. In HivePress, this can be done using [controllers](/developer-docs/framework/controllers) – PHP classes that define URL routes and implement actions corresponding to them.

Create a `class-followers.php` file in the `includes/controllers` extension subdirectory. The file and PHP class naming conventions are the same as for components.

```php
<?php
namespace HivePress\Controllers;

use HivePress\Helpers as hp;
use HivePress\Models;
use HivePress\Blocks;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Controller class.
 */
final class Followers extends Controller {

	/**
	 * Class constructor.
	 *
	 * @param array $args Controller arguments.
	 */
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				'routes' => [
					// Define custom URL routes here.
				],
			],
			$args
		);

		parent::__construct( $args );
	}
	
	// Implement the route actions here.
}
```

#### Add the template route

Let's define a new URL route in the controller constructor:

```php
'listings_feed_page' => [
	'title'     => esc_html__( 'Feed', 'foo-followers' ),
	'base'      => 'user_account_page',
	'path'      => '/feed',
	'redirect'  => [ $this, 'redirect_feed_page' ],
	'action'    => [ $this, 'render_feed_page' ],
	'paginated' => true,
],
```

{% hint style="info" %}
If you add new or change any of the existing URL routes, don't forget to refresh permalinks in the **Settings > Permalinks** section.
{% endhint %}

The route we defined uses these parameters:

* **title** - used for the page title and menu label;
* **base** - used for inheriting another route `path`;
* **path** - the relative route URL path;
* **redirect** - points to the URL redirect function;
* **action** - points to the route action function;
* **paginated** - flag required for paginated URLs.

Based on the parameter values, the `listings_feed_page` route has the `/account/feed` URL, renders a page with the “Feed” title and supports pagination.

Next, let's implement the redirect and action functions for this route below the constructor:

```php
/**
 * Redirects listing feed page.
 *
 * @return mixed
 */
public function redirect_feed_page() {

	// Check authentication.
	if ( ! is_user_logged_in() ) {
		return hivepress()->router->get_return_url( 'user_login_page' );
	}

	// Check followed vendors.
	if ( ! hivepress()->request->get_context( 'vendor_follow_ids' ) ) {
		return hivepress()->router->get_url( 'user_account_page' );
	}

	return false;
}

/**
 * Renders listing feed page.
 *
 * @return string
 */
public function render_feed_page() {

	// Create listing query.
	$query = Models\Listing::query()->filter(
		[
			'status'     => 'publish',
			'vendor__in' => hivepress()->request->get_context( 'vendor_follow_ids' ),
		]
	)->order( [ 'created_date' => 'desc' ] )
	->limit( get_option( 'hp_listings_per_page' ) )
	->paginate( hivepress()->request->get_page_number() );

	// Set request context.
	hivepress()->request->set_context(
		'post_query',
		$query->get_args()
	);

	// Render page template.
	return ( new Blocks\Template(
		[
			'template' => 'listings_feed_page',

			'context'  => [
				'listings' => [],
			],
		]
	) )->render();
}
```

When the route URL is visited, the redirect function is called first. In our case, it checks if the current user is logged in and has any followed vendor IDs. It returns the corresponding redirect URL or `false` if all checks are passed.

If there was no redirect, the action function is called next. As you can see, it creates a query for listings published by the followed vendors, sets it as the main page query, and finally renders the template we created earlier. Notice that this function returns the rendered HTML instead of outputting it with `echo`.

Now, if you refresh permalinks in **Settings > Permalinks** and try to visit the `/account/feed` URL, you will be redirected because you haven’t followed any vendors yet.

#### Update the component

We already used a code that checks if the current user follows any vendors by checking the `vendor_follow_ids` value in the request context, but there’s no function that sets this value in context yet. Add this code to the component constructor:

```php
add_filter( 'hivepress/v1/components/request/context', [ $this, 'set_request_context' ] );
```

The code above hooks a custom filtering function to the request context values. Let’s implement this function in the component below the constructor:

```php
/**
 * Sets request context for pages.
 *
 * @param array $context Context values.
 * @return array
 */
public function set_request_context( $context ) {

	// Get user ID.
	$user_id = get_current_user_id();

	// Get cached vendor IDs.
	$vendor_ids = hivepress()->cache->get_user_cache( $user_id, 'vendor_follow_ids', 'models/follow' );

	if ( is_null( $vendor_ids ) ) {

		// Get follows.
		$follows = Models\Follow::query()->filter(
			[
				'user' => $user_id,
			]
		)->get();

		// Get vendor IDs.
		$vendor_ids = [];

		foreach ( $follows as $follow ) {
			$vendor_ids[] = $follow->get_vendor__id();
		}

		// Cache vendor IDs.
		hivepress()->cache->set_user_cache( $user_id, 'vendor_follow_ids', 'models/follow', $vendor_ids );
	}

	// Set request context.
	$context['vendor_follow_ids'] = $vendor_ids;

	return $context;
}
```

This function checks if the followed vendor IDs are cached for the current user, and if not, it queries the `Follow` model objects by user ID and fills an array of vendor IDs, then caches this array. Finally, it sets an array of vendor IDs in the `vendor_follow_ids` context, this allows us to get it anywhere in the code this way:

```php
$vendor_ids = hivepress()->request->get_context( 'vendor_follow_ids' );
```

Also, the listing feed page we created doesn’t have any links on the front-end yet, so let’s add it to the user account menu. Add this code to the component constructor:

```php
add_filter( 'hivepress/v1/menus/user_account', [ $this, 'add_menu_item' ] );
```

The code above hooks a custom filtering function to the user account menu parameters. Next, implement this function in the component below the constructor:

```php
/**
 * Adds menu item to user account.
 *
 * @param array $menu Menu arguments.
 * @return array
 */
public function add_menu_item( $menu ) {
	if ( hivepress()->request->get_context( 'vendor_follow_ids' ) ) {
		$menu['items']['listings_feed'] = [
			'route'  => 'listings_feed_page',
			'_order' => 20,
		];
	}

	return $menu;
}
```

As you can see, it adds a custom menu item linked to the `listings_feed_page` route we created previously. You can adjust the `_order` parameter value to change the menu item position. The menu item will appear only if the current user follows any vendors.

#### Add REST API routes

Let’s also create URL routes that allow users to follow or unfollow vendors. Since these routes don’t render anything and are used for performing actions only, we will define them as [REST API](https://developer.wordpress.org/rest-api/) routes. These routes don’t require the `title`, `redirect`, and `paginated` parameters, but other parameters are needed instead:

* **method** - restricts the accepted HTTP method (e.g. `GET`, `POST`);
* **rest** - flag required for REST API routes.

{% hint style="info" %}
It’s good practice to follow the `{entity}-{context}-{type}` pattern for naming routes (e.g. `listing_view_page`, `vendor_update_action`).
{% endhint %}

```php
'vendor_follow_action'    => [
	'base'   => 'vendor_resource',
	'path'   => '/follow',
	'method' => 'POST',
	'action' => [ $this, 'follow_vendor' ],
	'rest'   => true,
],

'vendors_unfollow_action' => [
	'base'   => 'vendors_resource',
	'path'   => '/unfollow',
	'method' => 'POST',
	'action' => [ $this, 'unfollow_vendors' ],
	'rest'   => true,
],
```

The code above defines 2 REST API routes, both accept requests via the `POST` method. The first route will follow or unfollow a vendor on every subsequent request, while the second one will unfollow all vendors at once. Next, implement the action functions for these routes:

```php
/**
 * Follows or unfollows vendor.
 *
 * @param WP_REST_Request $request API request.
 * @return WP_Rest_Response
 */
public function follow_vendor( $request ) {

	// Check authentication.
	if ( ! is_user_logged_in() ) {
		return hp\rest_error( 401 );
	}

	// Get vendor.
	$vendor = Models\Vendor::query()->get_by_id( $request->get_param( 'vendor_id' ) );

	if ( ! $vendor || $vendor->get_status() !== 'publish' ) {
		return hp\rest_error( 404 );
	}

	// Get follows.
	$follows = Models\Follow::query()->filter(
		[
			'user'   => get_current_user_id(),
			'vendor' => $vendor->get_id(),
		]
	)->get();

	if ( $follows->count() ) {

		// Delete follows.
		$follows->delete();
	} else {

		// Add new follow.
		$follow = ( new Models\Follow() )->fill(
			[
				'user'   => get_current_user_id(),
				'vendor' => $vendor->get_id(),
			]
		);

		if ( ! $follow->save() ) {
			return hp\rest_error( 400, $follow->_get_errors() );
		}
	}

	return hp\rest_response(
		200,
		[
			'data' => [],
		]
	);
}

/**
 * Unfollows all vendors.
 *
 * @param WP_REST_Request $request API request.
 * @return WP_Rest_Response
 */
public function unfollow_vendors( $request ) {

	// Check authentication.
	if ( ! is_user_logged_in() ) {
		return hp\rest_error( 401 );
	}

	// Delete follows.
	$follows = Models\Follow::query()->filter(
		[
			'user' => get_current_user_id(),
		]
	)->delete();

	return hp\rest_response(
		200,
		[
			'data' => [],
		]
	);
}
```

The `follow_vendor` function checks if the current user is logged in, gets a `Vendor` object by ID, and queries the `Follow` objects by the user and vendor IDs.

Then, if any `Follow` objects are found, they are deleted. If not, a new `Follow` object is created and saved in the database. This way, every subsequent call of this function will follow or unfollow a vendor, creating or deleting a `Follow` object.

The `unfollow_vendors` function checks if the current user is logged in, then queries the `Follow` objects by the user ID and deletes them, thus unfollowing all vendors.

Now we have all the URL routes and actions according to the extension requirements. You can view the complete controller [source code](https://github.com/hivepress/foo-followers/blob/main/includes/controllers/class-followers.php) on GitHub for reference.

### Create a block

We created REST API routes, but there are no links or forms on the front-end that send requests to these routes yet. Let’s add a toggle link that sends a request to the `vendor_follow_action` on click and add it somewhere on the vendor page. We need to create a new block type for this.

In HivePress, [block types](/developer-docs/framework/blocks) are defined as PHP classes with properties and methods that determine the behavior and rendering of the block.

Create a `class-follow-toggle.php` file in the `includes/blocks` extension subdirectory. The file and PHP class naming conventions are the same as for components.

```php
<?php
namespace HivePress\Blocks;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Block class.
 */
class Follow_Toggle extends Toggle {

	/**
	 * Class constructor.
	 *
	 * @param array $args Block arguments.
	 */
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				'states' => [
					[
						'icon'    => 'user-plus',
						'caption' => esc_html__( 'Follow', 'foo-followers' ),
					],
					[
						'icon'    => 'user-minus',
						'caption' => esc_html__( 'Unfollow', 'foo-followers' ),
					],
				],
			],
			$args
		);

		parent::__construct( $args );
	}

	/**
	 * Bootstraps block properties.
	 */
	protected function boot() {

		// Get vendor from the block context.
		$vendor = $this->get_context( 'vendor' );

		if ( $vendor ) {

			// Set URL for sending requests on click.
			$this->url = hivepress()->router->get_url(
				'vendor_follow_action',
				[
					'vendor_id' => $vendor->get_id(),
				]
			);

			// Set active state if vendor is followed.
			if ( in_array(
				$vendor->get_id(),
				hivepress()->request->get_context( 'vendor_follow_ids', [] )
			) ) {
				$this->active = true;
			}
		}

		parent::boot();
	}
}
```

Notice that the block class is based on the `Toggle` class – this is an existing block type available in HivePress, so all the properties and methods are inherited from it.

The `Follow_Toggle` block type we created defines 2 states for the toggle, setting the [Font Awesome](https://fontawesome.com/icons) icon name and a label for each state.

Before the block is rendered, it fetches the `Vendor` object from the current template context and sets the toggle `url` to the `vendor_follow_action` route we created previously. It also enables the `active` flag if the vendor ID is among the followed vendor IDs.

This way, the toggle will show the “Follow” or “Unfollow” label on every subsequent click and send an AJAX request to the `vendor_follow_action` route URL. Also, it will show the “Unfollow” label by default if the vendor is already followed.

{% hint style="info" %}
It’s good practice to re-use the existing HivePress block types and avoid creating new ones if possible. In this case, we had to create a new block type because it implements a custom logic (the toggle state depends on the followed vendor IDs).
{% endhint %}

#### Update the component

Next, let’s add this block to the vendor templates. Add this code to the component constructor:

```php
add_filter( 'hivepress/v1/templates/vendor_view_block', [ $this, 'add_toggle_block' ] );
add_filter( 'hivepress/v1/templates/vendor_view_page', [ $this, 'add_toggle_block' ] );
```

The code above hooks a custom filtering function to the vendor template parameters. Now, implement this function in the component below the constructor:

```php
/**
 * Adds toggle block to vendor templates.
 *
 * @param array $template Template arguments.
 * @return array
 */
public function add_toggle_block( $template ) {
	return hp\merge_trees(
		$template,
		[
			'blocks' => [
				'vendor_actions_primary' => [
					'blocks' => [
						'vendor_follow_toggle' => [
							'type'       => 'follow_toggle',
							'_order'     => 50,

							'attributes' => [
								'class' => [ 'hp-vendor__action', 'hp-vendor__action--follow' ],
							],
						],
					],
				],
			],
		]
	);
}
```

The function above filters the template parameters and adds a new block using the block type we created earlier. Let’s check if the toggle link is added on the front-end:

![](/files/zffHiQoYCRJSLCzcegk3)

Now you can try clicking on the Follow toggle and check if the vendor listings appear on the listing feed page, and unfollow a vendor to check if listings disappear.

### Create a form

We still have one action left that is not used anywhere, the one that unfollows all vendors at once. So let’s create a form for it and add a button to show the form on click.

Create a `class-vendors-unfollow.php` file in the `includes/forms` extension subdirectory. The file and PHP class naming conventions are the same as for components.

```php
<?php
namespace HivePress\Forms;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Form class.
 */
class Vendors_Unfollow extends Form {

	/**
	 * Class constructor.
	 *
	 * @param array $args Form arguments.
	 */
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				'description' => esc_html__( 'Are you sure you want to unfollow all vendors?', 'foo-followers' ),
				'action'      => hivepress()->router->get_url( 'vendors_unfollow_action' ),
				'method'      => 'POST',
				'redirect'    => true,

				'button'      => [
					'label' => esc_html__( 'Unfollow', 'foo-followers' ),
				],
			],
			$args
		);

		parent::__construct( $args );
	}
}
```

The form we’ve created defines these parameters:

* **description** - text displayed before the form;
* **action** - URL for sending requests on submission;
* **method** - HTTP method for sending requests (e.g. `POST`, `GET`);
* **redirect** - flag to refresh or redirect the page;
* **button** - the submit button parameters.

{% hint style="info" %}
This form doesn’t contain fields, but you can define an array of fields in the `fields` parameter and the form will render them, sending the entered values with the request.
{% endhint %}

#### Update the template

Next, let’s add new blocks to the `Listings_Feed_Page` template we created earlier:

```php
'vendors_unfollow_link' => [
	'type'   => 'part',
	'path'   => 'vendor/follow/vendors-unfollow-link',
	'_order' => 30,
],

'vendors_unfollow_modal' => [
	'title'  => esc_html__( 'Unfollow Vendors', 'foo-followers' ),
	'type'   => 'modal',

	'blocks' => [
		'vendors_unfollow_form' => [
			'type' => 'form',
			'form' => 'vendors_unfollow',
		],
	],
],
```

As you can see, there’s a `part` block that loads a specific HTML file and a `modal` block that contains the form we’ve just created. The part block points to a non-existing file, so we need to create a `vendors-unfollow-link.php` file in the `templates/vendor/follow` extension subdirectory:

```php
<?php
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;
?>
<a href="#vendors_unfollow_modal" class="button"><?php esc_html_e( 'Unfollow Vendors', 'foo-followers' ); ?></a>
```

{% hint style="info" %}
It’s good practice to follow the `{entity}/{context}/{layout}` directory structure for template parts, this way you can easily find the template where the part is used.
{% endhint %}

Now, let’s check the listing feed page. It should have the “Unfollow” button that opens a modal window on click. The modal window contains a form that sends a request to the `vendors_unfollow_action` route URL, thus unfollowing all vendors at once.

![](/files/ZCSGyvK1aBtPx3BtxRm3)

### Create an email

Finally, let’s add an email notification sent to users about new listings from the followed vendors.

Create a `class-listing-feed.php` file in the `includes/emails` extension subdirectory. The file and PHP class naming conventions are the same as for components.

```php
<?php
namespace HivePress\Emails;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Email class.
 */
class Listing_Feed extends Email {

	/**
	 * Class constructor.
	 *
	 * @param array $args Email arguments.
	 */
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				'subject' => esc_html__( 'New Listing', 'foo-followers' ),
				'body'    => esc_html__( 'Hi, %user_name%! There is a new listing "%listing_title%" in your feed, click on the following link to view it: %listing_url%', 'foo-followers' ),
			],
			$args
		);

		parent::__construct( $args );
	}
}
```

The email we’ve just created defines these parameters:

* **subject** - the email subject;
* **body** - the email message with placeholders.

#### Update the component

Next, let’s add a function that sends an email to the vendor followers if there’s a newly published listing. Add this code to the component constructor:

```php
add_action( 'hivepress/v1/models/listing/update_status', [ $this, 'send_feed_emails' ], 10, 4 );
```

The code above hooks a custom function to the listing status change action. Now, implement this function in the component below the constructor:

```php
/**
 * Sends emails about a new listing.
 *
 * @param int    $listing_id Listing ID.
 * @param string $new_status New status.
 * @param string $old_status Old status.
 * @param object $listing Listing object.
 */
public function send_feed_emails( $listing_id, $new_status, $old_status, $listing ) {

	// Check listing status.
	if ( 'publish' !== $new_status || ! in_array( $old_status, [ 'auto-draft', 'pending' ] ) ) {
		return;
	}

	// Get follows.
	$follows = Models\Follow::query()->filter(
		[
			'vendor' => $listing->get_vendor__id(),
		]
	)->get();

	foreach ( $follows as $follow ) {

		// Get user.
		$user = $follow->get_user();

		// Send email.
		( new Emails\Listing_Feed(
			[
				'recipient' => $user->get_email(),

				'tokens'    => [
					'user_name'     => $user->get_display_name(),
					'listing_title' => $listing->get_title(),
					'listing_url'   => hivepress()->router->get_url( 'listing_view_page', [ 'listing_id' => $listing->get_id() ] ),
				],
			]
		) )->send();
	}
}
```

The function above checks if the listing got the "published" status, gets all the `Follow` objects by vendor ID and sends an email to each follower, providing the email address and tokens to be replaced in the email text.

Now, if you follow a vendor and this vendor publishes a new listing, you will get an email notification that contains the listing title and URL.

### Create a POT file

You probably noticed that we wrapped all the texts in the code with the [translation functions](https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/). We also need to generate a POT file for translation to work properly.

Create a new `languages` extension subdirectory and install the [Loco Translate](https://wordpress.org/plugins/loco-translate/) plugin. Go to **Loco Translate > Plugins > Followers for HivePress** and click **Create template**, then proceed.

That's it. Now website owners can translate or change any of the extension texts via Loco Translate or POEdit without editing the source code directly.

### Keep developing

Congratulations! You’ve just developed a fully-functional HivePress extension. Even though there's a lot more to the HivePress framework than what you’ve seen so far, you’re now ready to start developing your own HivePress extensions.

For example, you can develop a custom HivePress extension for a client, share it on the [WordPress.org](https://wordpress.org/plugins/developers/add/) repository or even sell it on the [CodeCanyon](https://codecanyon.net/) marketplace.

If you have any questions about the HivePress framework, please check the available docs and feel free to join the HivePress [developer community](https://community.hivepress.io/).


# Blocks

In HivePress, [templates](/developer-docs/framework/templates) are defined as arrays of blocks. With blocks, it’s easy to re-use and customize specific layout parts without affecting the whole template.

Each block type is implemented as a PHP class with properties and methods that determine the behavior and rendering of the block HTML content. You can use any of the existing block types in templates or even create a new one if required.

### Creating block types

If you are developing a custom HivePress [extension](/developer-docs/tutorials/create-a-custom-hivepress-extension), you may need to create a new block type. To do this, create a new `class-{block-type}.php` file (use lowercase letters, numbers, and hyphens only) in the `includes/blocks` extension subdirectory and HivePress will load it automatically.

{% hint style="info" %}
It’s good practice to re-use the existing HivePress block types and avoid creating new ones if possible. For example, a new type may be required if you need a completely new UI element, or if it requires some custom logic.
{% endhint %}

Then, define the block type PHP class. The class name should be based on the file name but with underscores instead of hyphens and no lowercase restriction (e.g. `Foo_Bar` class for the `class-foo-bar.php` file). Pick a name that is unique enough to avoid conflicts with other HivePress block types.

```php
<?php
namespace HivePress\Blocks;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

class Foo_Bar extends Block {

	// Declare the block properties.
	protected $say_hello;

	public function __construct( $args = [] ) {

		// Set the property defaults.
		$args = hp\merge_arrays(
			[
				'say_hello' => false,
			],
			$args
		);

		parent::__construct( $args );
	}

	public static function init( $meta = [] ) {

		// Add label and settings for Gutenberg.
		$meta = hp\merge_arrays(
			[
				'label'    => 'Hello World',

				'settings' => [
					'say_hello' => [
						'label'  => 'Say Hello',
						'type'   => 'checkbox',
						'_order' => 123,
					],
				],
			],
			$meta
		);

		parent::init( $meta );
	}

	protected function boot() {
		// Do something after the block is loaded.

		parent::boot();
	}

	public function render() {

		// Render the block HTML content.
		$output = '<h1>';

		if ( $this->say_hello ) {
			$output .= 'Hello World!';
		} else {
			$output .= 'Silence...';
		}

		$output .= '</h1>';

		return $output;
	}
}

```

The code example above defines a new `Foo_Bar` block type. It has a single `say_hello` property set to `false` by default. This block type is also registered in the Gutenberg editor with the "Hello World" label and a single checkbox option for changing the `say_hello` property value. If this property is set to `true`, the block outputs the **H1** heading with "Hello World!" text. If not, the "Silence..." text is displayed instead.

Registering the block type in Gutenberg via the `init` method is required only if it's not template-specific and you want to allow adding this block to any page in **WordPress > Pages**. Setting a label is enough to register the block type, but you can also define the extra block settings as an array of [fields](/developer-docs/framework/fields) in the `settings` parameter.

Also, a custom block type can be based on another type's PHP class, allowing you to re-use its properties and methods without repeating the code:

```php
<?php
namespace HivePress\Blocks;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

class Foo_Bar extends Container {

	// Override properties and methods of Container type.
}

```

In the same way, you can create custom block types for your HivePress extension.

### Customizing block types

You can customize any of the existing block types using [hooks](https://hivepress.github.io/hook-reference/). For example, the code below adds a new checkbox option to the **Listings** block settings in the Gutenberg editor. It allows users to check and save it after inserting the **Listings** block into the editor.

```php
add_filter(
	'hivepress/v1/blocks/listings/meta',
	function( $meta ) {
		$meta['settings']['my_custom_option'] = [
			'label'  => 'My Custom Option',
			'type'   => 'checkbox',
			'_order' => 123,
		];

		return $meta;
	}
);
```

The code example below checks if the option we've just added is checked and adds a custom CSS class to the **Listings** block HTML container.

```php
add_filter(
	'hivepress/v1/blocks/listings',
	function( $args ) {
		if ( isset( $args['my_custom_option'] ) && $args['my_custom_option'] ) {
			$args = hivepress()->helper->merge_arrays(
				$args,
				[
					'attributes' => [
						'class' => [ 'my-custom-class' ],
					],
				]
			);
		}

		return $args;
	}
);
```

Similarly, you can customize any block type in HivePress or its extensions.


# Callback

This block type calls a custom function and renders its output.

### Parameters

* **callback** - callable function name;
* **params** - an array of function arguments;
* **return** - set to `true` if the function returns the result instead of echoing it.

### Example

The code below renders the WordPress site title. It calls the WordPress [get\_bloginfo](https://developer.wordpress.org/reference/functions/get_bloginfo/) function with the `show` parameter set to "name". The `return` block parameter is set to `true` because this function returns the result instead of echoing it.

```php
echo ( new HivePress\Blocks\Callback(
	[
		'callback' => 'get_bloginfo',
		'params'   => [ 'name' ],
		'return'   => true,
	]
) )->render();
```


# Container

This block type renders inner blocks with an optional HTML wrapper.

### Parameters

* **tag** - tag name for the HTML wrapper, set to `false` to disable it;
* **optional** - set to `true` if the wrapper is not required for empty content;
* **attributes** - an array of HTML attributes for the wrapper;
* **blocks** - an array of parameters for the inner blocks.

### Example

The code below renders an HTML `section` with `my-custom-class` CSS class, but only if the rendered content is not empty. There's a single inner block of the `content` type that outputs the "Hello World!" text.

```php
echo ( new HivePress\Blocks\Container(
	[
		'tag'        => 'section',
		'optional'   => true,

		'attributes' => [
			'class' => [ 'my-custom-class' ],
		],

		'blocks'     => [
			'my_custom_block' => [
				'type'    => 'content',
				'content' => 'Hello World!',
				'_order'  => 123,
			],
		],
	]
) )->render();
```


# Content

This block type renders the provided text or HTML content.

### Parameters

* **content** - text or HTML content to output.

### Example

The code below outputs the "Hello World!" text.

```php
echo ( new HivePress\Blocks\Content(
	[
		'content' => 'Hello World!',
	]
) )->render();
```


# Form

This block type renders a [form](/developer-docs/framework/forms).

### Parameters

* **form** - the name of a form to render;
* **redirect** - `true` to refresh or URL to redirect the form on success;
* **values** - optional field values to pre-fill;
* **attributes** - the form HTML attributes.

### Example

The code below renders the user registration form with an extra `my-custom-class` CSS class, and "<user@example.com>" default value in the **Email** field, refreshing the page on success.

```php
echo ( new HivePress\Blocks\Form(
	[
		'form'       => 'user_register',
		'redirect'   => true,

		'values'     => [
			'email' => 'user@example.com',
		],

		'attributes' => [
			'class' => [ 'my-custom-class' ],
		],
	]
) )->render();
```


# Menu

This block type renders a [menu](/developer-docs/framework/menus).

### Parameters

* **menu** - the name of a menu to render;
* **attributes** - the menu HTML attributes.

### Example

The code below renders the user account menu with an extra `my-custom-class` CSS class.

```php
echo ( new HivePress\Blocks\Menu(
	[
		'menu'       => 'user_account',

		'attributes' => [
			'class' => [ 'my-custom-class' ],
		],
	]
) )->render();
```


# Modal

This block type renders a modal window with inner blocks.

### Parameters

* **title** - the modal window title;
* **model** - an optional [model](/developer-docs/framework/models) name to make the window ID unique;
* all the parameters from the [Container](/developer-docs/framework/blocks/container) block type.

### Example

The code below renders a modal window with the "My Custom Text" title and a single inner block of the `content` type that outputs the "Hello World!" text. If you add a link with the `#my_custom_modal` URL anywhere on a page, it will open this modal window on click.

```php
echo ( new HivePress\Blocks\Modal(
	[
		'title'  => 'My Custom Text',
		'name'   => 'my_custom_modal',

		'blocks' => [
			'my_custom_block' => [
				'type'    => 'content',
				'content' => 'Hello World!',
				'_order'  => 123,
			],
		],
	]
) )->render();
```


# Part

This block type renders a template part by the provided path.

### Parameters

* **path** - the PHP file path, without the extension and relative to the `templates` subdirectory of HivePress or its extensions.

### Example

The code below renders the `no-results-message.php` file from the `templates/page` HivePress subdirectory, this template part contains the "Nothing found" message.

```php
echo ( new HivePress\Blocks\Part(
	[
		'path' => 'page/no-results-message',
	]
) )->render();
```


# Section

This block type renders an HTML section with inner blocks.

### Parameters

* **title** - the section title;
* all the parameters from the [Container](/developer-docs/framework/blocks/container) block type.

### Example

The code below renders an HTML section with the "My Custom Text" title and a single inner block of the `content` type that outputs the "Hello World!" text.

```php
echo ( new HivePress\Blocks\Section(
	[
		'title'  => 'My Custom Text',

		'blocks' => [
			'my_custom_block' => [
				'type'    => 'content',
				'content' => 'Hello World!',
				'_order'  => 123,
			],
		],
	]
) )->render();
```


# Template

This block type renders a [template](/developer-docs/framework/templates).

### Parameters

* **template** - the name of a template to render.

### Example

The code below renders the **Listings** page template.

```php
echo ( new HivePress\Blocks\Template(
	[
		'template' => 'listings_view_page',
	]
) )->render();
```


# Toggle

This block type renders a toggle link that sends an AJAX request on every subsequent click, changing its icon and label depending on the current state.

### Parameters

* **view** - the link view, set to `icon` if labels are not required;
* **url** - the URL for sending AJAX requests on click;
* **states** - an array of the link state parameters;
* **attributes** - the link HTML attributes;
* **active** - `true` if the toggle link is currently active.

### Example

The code below renders a toggle link that displays the "Follow" text with the `user-plus` [Font Awesome](https://fontawesome.com/v6/icons/) icon when inactive and the "Unfollow" text with the `user-minus` icon when active. It sends an AJAX request to the `https://example.com` URL, changing the link icon and label on every subsequent click.

```php
echo ( new HivePress\Blocks\Toggle(
	[
		'url'    => 'https://example.com',

		'states' => [
			[
				'icon'    => 'user-plus',
				'caption' => 'Follow',
			],
			[
				'icon'    => 'user-minus',
				'caption' => 'Unfollow',
			],
		],
	]
) )->render();
```


# Components

In HivePress, components are PHP classes that are used to group actions, filters, and helper functions. For example, the **Router** component contains all the callbacks and helper functions for managing URLs and redirects.

If the component class has a public method, you can use it as a helper and call it from anywhere via the `hivepress` function and the component name. For example, the **Router** component has the `get_url` method, so it can be called this way:

```php
$url = hivepress()->router->get_url( 'my_custom_route' );
```

Components are also useful for adding action and filter callbacks. The class constructor is used for attaching callbacks to hooks with the `add_filter` and `add_action` functions, while the public class methods are used as callbacks.

### Creating components <a href="#bkmrk-creating-components" id="bkmrk-creating-components"></a>

If you are developing a custom HivePress [extension](/developer-docs/tutorials/create-a-custom-hivepress-extension), you should create a default component for it. To do this, create a new `class-{extension-name}.php` file (use lowercase letters, numbers, and hyphens only) in the `includes/components` extension subdirectory and HivePress will load it automatically.

Then, define the component PHP class. The class name should be based on the file name but with underscores instead of hyphens and no lowercase restriction (e.g. `Foo_Bar` class for the `class-foo-bar.php` file). Pick a name that is unique enough to avoid conflicts with other HivePress components.

```php
<?php
namespace HivePress\Components;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

final class Foo_Bar extends Component {
	public function __construct( $args = [] ) {

		// Attach functions to hooks here (e.g. add_action, add_filter).
		add_action( 'template_redirect', [ $this, 'redirect_page' ] );

		parent::__construct( $args );
	}

	public function redirect_page() {
		// Implement the attached function here.
	}

	public function do_something() {
		// Implement the helper function here.
	}
}
```

The code above defines a new `Foo_Bar` component with a single action callback executed on every page load and a public method that can be called from anywhere in the code:

```php
hivepress()->foo_bar->do_something();
```

Similarly, you can implement the callbacks and helpers for your extension within a single file.


# Cache

This component implements callbacks and methods for managing cache using WordPress [Transients](https://developer.wordpress.org/apis/handbook/transients/) and [Metadata](https://developer.wordpress.org/apis/handbook/metadata/) API. By default, the cached values are stored in the database, but you can switch to the in-memory cache with [Redis](https://redis.io/) or [Memcached](https://memcached.org/). If you need to disable the HivePress cache, set the `HP_CACHE` constant to `false`:

```php
define( 'HP_CACHE', false );
```

### Quick example <a href="#bkmrk-quick-example" id="bkmrk-quick-example"></a>

The function below counts the number of listings added by a specific user. It caches the listing count to prevent the calculation on every call and stores the cache until some listing is added, removed or updated.

```php
function get_listing_count( $user_id ) {

	// Get the cached value.
	$listing_count = hivepress()->cache->get_user_cache( $user_id, 'listing_count', 'models/listing' );

	if ( is_null( $listing_count ) ) {

		// Count listings.
		$listing_count = HivePress\Models\Listing::query()->filter(
			[
				'user'       => $user_id,
				'status__in' => [ 'draft', 'pending', 'publish' ],
			]
		)->get_count();

		// Cache the calculated value.
		hivepress()->cache->set_user_cache( $user_id, 'listing_count', 'models/listing', $listing_count );
	}

	return $listing_count;
}
```

### Storing cache <a href="#bkmrk-storing-cache" id="bkmrk-storing-cache"></a>

To store a value in cache, call the `set_cache` method with the cache key:

```php
hivepress()->cache->set_cache( 'custom_key', null, 'custom_value' );
```

For grouping the cached values, provide the group name. Both the key and the group name should be as unique as possible to avoid conflicts with other cached values.

```php
hivepress()->cache->set_cache( 'custom_key', 'custom_group', 'custom_value' );
```

You can also set a time limit for storing the cached value:

```php
hivepress()->cache->set_cache( 'custom_key', 'custom_group', 'custom_value', DAY_IN_SECONDS );
```

Set the [model](/developer-docs/framework/models) name as a cache group to clear cache automatically when the model objects are updated. For example, the code below clears the cached value if any listing is added, removed, or updated:

```php
hivepress()->cache->set_cache( 'custom_key', 'models/listing', 'custom_value' );
```

Similarly, you can store the cache for a specific user, post, comment or term by ID. For example, if you count the number of listings for a user with ID `123`, you can store it this way:

```php
hivepress()->cache->set_user_cache( 123, 'listing_count', 'models/listing', $listing_count );
```

The `set_post_cache`, `set_comment_cache` and `set_term_cache` methods accept the same parameters.

### Retrieving cache <a href="#bkmrk-retrieving-cache" id="bkmrk-retrieving-cache"></a>

To retrieve a cached value, call the `get_cache` method with the cache key:

```php
$value = hivepress()->cache->get_cache( 'custom_key' );
```

It returns `null` if the cache doesn't exist. For retrieving cache related to some group, provide the group name:

```php
$value = hivepress()->cache->get_cache( 'custom_key', 'custom_group' );
```

Similarly, you can retrieve the cache for a specific user, post, comment or term by ID. For example, HivePress caches the number of listings added by each user, and you can retrieve it this way:

```php
$listing_count = hivepress()->cache->get_user_cache( 123, 'listing_count', 'models/listing' );
```

The `get_post_cache`, `get_comment_cache` and `get_term_cache` methods accept the same parameters.

### Clearing cache <a href="#bkmrk-clearing-cache" id="bkmrk-clearing-cache"></a>

The cache is cleared automatically if the time limit is reached or the related model objects are updated. Also, if the in-memory cache is enabled, it may be cleared if the maximum allowed storage is exceeded.

You can clear the cache explicitly by calling the `delete_cache` method with the cache key:

```php
hivepress()->cache->delete_cache( 'custom_key' );
```

To clear the cache related to some group, provide the group name:

```php
hivepress()->cache->delete_cache( 'custom_key', 'custom_group' );
```

For clearing the whole cache group, skip the cache key parameter:

```php
hivepress()->cache->delete_cache( null, 'custom_group' );
```

Similarly, you can clear the cache for a specific user, post, comment or term by ID. For example, HivePress caches the number of listings added by each user, and you can clear it this way:

```php
hivepress()->cache->delete_user_cache( 123, 'listing_count', 'models/listing' );
```

The `delete_post_cache`, `delete_comment_cache` and `delete_term_cache` methods accept the same parameters.


# Helper

This component acts as a proxy for all the HivePress helper functions defined in the `includes/helpers.php` file. With the **Helper** component, you can call a helper function anywhere in the code without declaring the PHP namespace. For example, the code below calls the `sanitize_html` helper that removes malicious HTML tags.

```php
hivepress()->helper->sanitize_html( 'custom HTML content' );
```


# Request

This component implements callbacks and methods for managing the page request context. With the request context, you can get the request-specific data anywhere in the code rather than passing it to each function that runs during a request.

### Request context <a href="#bkmrk-request-context" id="bkmrk-request-context"></a>

To set a request context value, call the `set_context` method with the key and a value:

```php
hivepress()->request->set_context( 'custom_key', $value );
```

Once the context value is set, you can get it anywhere by calling the `get_context` method:

```php
$value = hivepress()->request->get_context( 'custom_key' );
```

Use the `hivepress/v1/components/request/context` hook that filters the request context array if you need to set some context values on every page load. There are also a few pre-defined context values. For example, you can get the current user object this way:

```php
$user = hivepress()->request->get_user();
```

If the current user is logged in, the `User` [model](/developer-docs/framework/models) object is returned. Also, you can get the current page number for paginated queries:

```php
$page = hivepress()->request->get_page_number();
```

### Query variables <a href="#bkmrk-query-variables" id="bkmrk-query-variables"></a>

To get a HivePress-specific [query variable](https://developer.wordpress.org/reference/functions/get_query_var/) (prefixed with `hp_`), call the `get_param` method with the variable name:

```php
$value = hivepress()->request->get_param( 'custom_name' );
```

You can also call the `get_params` method to get an array of all the HivePress query variables.


# Router

This component implements callbacks and methods for managing URLs and redirects. In HivePress, each URL is registered as a route with an array of parameters. This makes URLs customizable and accessible via the **Router** methods.

To get the current route name, call the `get_current_route_name` method:

```php
$route = hivepress()->router->get_current_route_name();
```

You can also customize any URL route via the `hivepress/v1/routes` filter hook. Remember to refresh permalinks in **Settings > Permalinks** after making any URL changes.

### Quick example <a href="#bkmrk-quick-example" id="bkmrk-quick-example"></a>

The code example below redirects non-registered users from the listing page to the login page. After the user is logged in or registered, there's a redirect back to the initial listing page.

```php
add_action(
	'template_redirect',
	function() {
		if ( ! is_user_logged_in() && hivepress()->router->get_current_route_name() === 'listing_view_page' ) {
			wp_safe_redirect( hivepress()->router->get_return_url( 'user_login_page' ) );

			exit;
		}
	}
);
```

### Getting URLs <a href="#bkmrk-getting-urls" id="bkmrk-getting-urls"></a>

To get the current URL, call the `get_current_url` method:

```php
$url = hivepress()->router->get_current_url();
```

For getting a URL by the route name, use the `get_url` method:

```php
$url = hivepress()->router->get_url( 'my_custom_route' );
```

Some routes require extra URL parameters. The code example below gets a listing URL by ID:

```php
$url = hivepress()->router->get_url( 'listing_view_page', [ 'listing_id' => 123 ] );
```

To get a URL that redirects users back after some action (e.g. logging in), use the `get_return_url` method:

```php
$url = hivepress()->router->get_return_url( 'my_custom_route' );
```

If you need to get the redirect URL from the current URL query parameters, call the `get_redirect_url` method:

```php
$url = hivepress()->router->get_redirect_url();
```


# Translator

This component implements the translation callbacks and methods. It has a useful `get_string` method for getting a string from the `strings` [configuration](/developer-docs/framework/configurations). For example, you can get the "Listings" word this way:

```php
hivepress()->translator->get_string( 'listings' );
```

You can also get the current language and region codes with the `get_language` and `get_region` methods.


# Configurations

In HivePress, configurations are defined as PHP arrays of parameters. For example, the `post_types` configuration contains parameters for registering the custom [post types](https://developer.wordpress.org/plugins/post-types/registering-custom-post-types/). Configuration files are stored in the `includes/configs` subdirectory of HivePress and its extensions. You can retrieve or extend configurations via the HivePress API.

### Creating configurations <a href="#bkmrk-creating-configurati" id="bkmrk-creating-configurati"></a>

If you are developing a custom HivePress [extension](/developer-docs/tutorials/create-a-custom-hivepress-extension), you may need to create a new configuration. To do this, create a new `{config-name}.php` file (use lowercase letters, numbers, and hyphens only) in the `includes/configs` extension subdirectory and HivePress will load it automatically. Pick a name unique enough to avoid conflicts with other HivePress configurations. For this example, we will name it `foo-bar.php`.

After the file is created, add the `return` statement with an array of parameters:

```php
return [
	'one'   => 'One',
	'two'   => 'Two',
	'three' => 'Three',
];
```

### Retrieving configurations <a href="#bkmrk-retrieving-configura" id="bkmrk-retrieving-configura"></a>

To retrieve a configuration, call the core `get_config` method with the configuration name:

```php
$config = hivepress()->get_config( 'foo_bar' );
```

The configuration name is the same as its filename but with underscores instead of hyphens. In the same way, you can retrieve any of the available configurations. For example, you can get an array of post types registered by HivePress:

```php
$post_types = hivepress()->get_config( 'post_types' );
```

### Extending configurations <a href="#bkmrk-extending-configurat" id="bkmrk-extending-configurat"></a>

To extend the configuration array, use the `hivepress/v1/{config_name}` filter hook. The code below adds a new array item to the `foo_bar` configuration:

```php
add_filter(
	'hivepress/v1/foo_bar',
	function( $config ) {
		$config['four'] = 'Four';

		return $config;
	}
);
```

Also, if you create a configuration file with the name of an existing one, HivePress will merge both automatically. For example, if you want to register a custom post type for your extension, simply create the `post-types.php` file in the `includes/configs` subdirectory, add an array of post type parameters, and HivePress will register it automatically.


# Comment types

This configuration contains parameters of the comment types used in HivePress. Currently, the only available parameter is `public`. If you are developing a custom HivePress [extension](/developer-docs/tutorials/create-a-custom-hivepress-extension) and want to hide a specific comment type everywhere, set the `public` parameter to `false`.

```php
'my_comment_type' => [
	'public' => false,
],
```

For example, in HivePress, private messages are implemented as hidden comments of `hp_message` type, so this parameter is set to `false` for the `message` comment type.


# Image sizes

This configuration contains parameters that HivePress uses for registering the custom image sizes. Each image size is defined as an array of parameters accepted by the [add\_image\_size](https://developer.wordpress.org/reference/functions/add_image_size/) function. The array key is used as the image size name (prefixed with `hp_`).

The code example below changes the `landscape_small` image size parameters. In the same way, you can customize any of the available image sizes or register a new one by adding an array with the image size parameters. Also, if you set the `label` parameter, HivePress will display settings for this image size in the **Settings > Media** section.

```php
add_filter(
	'hivepress/v1/image_sizes',
	function( $image_sizes ) {
		$image_sizes['landscape_small']['height'] = 123;

		return $image_sizes;
	}
);
```


# Meta boxes

This configuration contains parameters that HivePress uses for registering the custom [meta boxes](https://developer.wordpress.org/plugins/metadata/custom-meta-boxes/). Each meta box is defined as an array of the following parameters:

* **title** - the meta box title;
* **screen** - an array of post types or taxonomy names (without the `hp_` prefix) to display the meta box for;
* **fields** - an array of meta box fields, each field must be defined as an array of the [field](/developer-docs/framework/fields) parameters.

The code example below adds a new meta box with a single checkbox field to the back-end listing edit page. By default, the field value is saved to the post (or term) meta. To save the field value to one of the standard post (or term) fields, use the `_alias` parameter.

```php
add_filter(
	'hivepress/v1/meta_boxes',
	function( $meta_boxes ) {
		$meta_boxes['custom_meta_box'] = [
			'title'  => 'Custom Meta Box',
			'screen' => 'listing',

			'fields' => [
				'custom_option' => [
					'label'  => 'Custom Option',
					'type'   => 'checkbox',
					'_order' => 123,
				],
			],
		];

		return $meta_boxes;
	}
);
```

In the same way, you can customize any of the available meta boxes or register a new one by adding an array with the meta box parameters. For example, the code below adds a new text field to the **Listing Settings** meta box.

```php
add_filter(
	'hivepress/v1/meta_boxes/listing_settings',
	function( $meta_box ) {
		$meta_box['fields']['custom_field'] = [
			'label'  => 'Custom Field',
			'type'   => 'text',
			'_order' => 123,
		];

		return $meta_box;
	}
);
```


# Post types

This configuration contains parameters that HivePress uses for registering the custom post types. Each post type is defined as an array of parameters accepted by the [register\_post\_type](https://developer.wordpress.org/reference/functions/register_post_type/) function. The array key is used as the post type name (prefixed with `hp_`).

The code example below changes the listing URL slug and the vendor icon in the WordPress dashboard menu. In the same way, you can customize any of the available post types or register a new one by adding an array with the post type parameters.

```php
add_filter(
	'hivepress/v1/post_types',
	function( $post_types ) {

		// Change listing URL slug.
		$post_types['listing']['rewrite']['slug'] = 'custom-slug';

		// Change vendor menu icon.
		$post_types['vendor']['menu_icon'] = 'dashicons-building';

		return $post_types;
	}
);
```


# Scripts

This configuration contains parameters that HivePress uses for loading the JS scripts. Each script is defined as an array of parameters accepted by the [wp\_enqueue\_script](https://developer.wordpress.org/reference/functions/wp_enqueue_script/) function. Also, there's an extra `scope` parameter that accepts the following values:

* **frontend** - for loading scripts on the front-end;
* **backend** - for loading scripts on the back-end.

You can set a single value or an array of values. If the `scope` parameter is not set, the script will be loaded on the front-end only.

The code example below adds a new dependency to the Slick slider script loaded by HivePress. In the same way, you can customize any of the available scripts or load a new one by adding an array with the script parameters.

```php
add_filter(
	'hivepress/v1/scripts',
	function( $scripts ) {
		$scripts['slick']['deps'][] = 'jquery';

		return $scripts;
	}
);
```


# Settings

This configuration contains parameters that HivePress uses for registering the custom settings. Each setting is defined as an array of HivePress [field](/developer-docs/framework/fields) parameters. The array key is used as the setting name (prefixed with `hp_`).

The code example below adds a new checkbox setting to the **HivePress > Settings > Listings > Display** section. In the same way, you can customize any of the available settings or register a new one by adding an array with the field parameters.

```php
add_filter(
	'hivepress/v1/settings',
	function( $settings ) {
		$settings['listings']['sections']['display']['fields']['custom_option'] = [
			'label'  => 'Custom Option',
			'type'   => 'checkbox',
			'_order' => 123,
		];

		return $settings;
	}
);
```

After the setting is added, you can get its value by calling the [get\_option](https://developer.wordpress.org/reference/functions/get_option/) function with the setting name (prefixed with `hp_`):

```php
$value = get_option( 'hp_custom_option' );
```


# Strings

This configuration contains an array of reusable HivePress strings. It's mainly used to avoid duplicating translations in the HivePress themes and extensions. You can get any of the available strings by the string name:

```php
echo hivepress()->translator->get_string( 'listings' );
```

The code example below changes the "Listings" word in HivePress along with its themes and extensions. In the same way, you can change any of the available strings or add a new one by adding an array item.

```php
add_filter(
	'hivepress/v1/strings',
	function( $strings ) {
		$strings['listings'] = 'Custom Text';

		return $strings;
	}
);
```


# Styles

This configuration contains parameters that HivePress uses for loading the CSS styles. Each style is defined as an array of parameters accepted by the [wp\_enqueue\_style](https://developer.wordpress.org/reference/functions/wp_enqueue_style/) function. Also, there's an extra `scope` parameter that accepts the following values:

* **frontend** - for loading styles on the front-end;
* **backend** - for loading styles on the back-end;
* **editor** - for loading styles in the page editor.

You can set a single value or an array of values. If the `scope` parameter is not set, the style will be loaded on the front-end only.

The code example below removes the Font Awesome styles loaded by HivePress. In the same way, you can customize any of the available styles or load a new one by adding an array with the style parameters.

```php
add_filter(
	'hivepress/v1/styles',
	function( $styles ) {
		unset( $styles['fontawesome'] );
		unset( $styles['fontawesome_solid'] );

		return $styles;
	}
);
```


# Taxonomies

This configuration contains parameters that HivePress uses for registering the custom taxonomies. Each taxonomy is defined as an array of parameters accepted by the [register\_taxonomy](https://developer.wordpress.org/reference/functions/register_taxonomy/) function. The array key is used as the taxonomy name (prefixed with `hp_`).

The code example below changes the listing category URL slug and its label in the WordPress dashboard menu. In the same way, you can customize any of the available taxonomies or register a new one by adding an array with the taxonomy parameters.

```php
add_filter(
	'hivepress/v1/taxonomies',
	function( $taxonomies ) {

		// Change URL slug.
		$taxonomies['listing_category']['rewrite']['slug'] = 'custom-slug';

		// Change menu label.
		$taxonomies['listing_category']['labels']['name'] = 'Custom Text';

		return $taxonomies;
	}
);
```


# Controllers

In HivePress, controllers are PHP classes that are used to define URL routes and implement actions corresponding to them. For example, the `Listing` controller contains all the routes and actions related to listings (e.g. viewing, creating, updating and deleting).

### Creating controllers

If you are developing a HivePress [extension](/developer-docs/tutorials/create-a-custom-hivepress-extension) that requires custom URL routes, you should create a new controller. To do this, create a new `class-{extension-name}.php` file (use lowercase letters, numbers, and hyphens only) in the `includes/controllers` extension subdirectory and HivePress will load it automatically.

Then, define the controller PHP class. The class name should be based on the file name but with underscores instead of hyphens and no lowercase restriction (e.g. `Foo_Bar` class for the `class-foo-bar.php` file). Pick a name that is unique enough to avoid conflicts with other HivePress controllers.

```php
<?php
namespace HivePress\Controllers;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

final class Foo_Bar extends Controller {
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				'routes' => [
					// Define custom URL routes here.
				],
			],
			$args
		);

		parent::__construct( $args );
	}
	
	// Implement the route actions here.
}
```

### Adding routes

Each URL route is defined as an array of parameters in the controller's class constructor and can have the redirect and action functions implemented as the class methods.

{% hint style="info" %}
If you add new or change any of the existing URL routes, don't forget to refresh permalinks in the **Settings > Permalinks** section.
{% endhint %}

Once you add a custom route, you can get its URL anywhere by the route name:

```php
$url = hivepress()->router->get_url( 'my_custom_route' );
```

#### Template routes

These routes are used for redirecting and rendering the page [templates](/developer-docs/framework/templates). For example, when you visit the listing edit page, the `listing_edit_page` route checks if you have permission to edit this listing (or redirects you otherwise) and then renders the page template.

To define a new URL route, add an array of the following parameters:

* **title** - text used as the page title and [menu](/developer-docs/framework/menus) label;
* **path** - the relative route URL path (starts with a slash);
* **base** - the route name to inherit the `path` from;
* **redirect** - points to the route redirect function;
* **action** - points to the route action function.

The code below defines a route with the `/custom-route` URL that checks if the current user is logged in and, if so, redirects to the account page. If not, it renders the login page template.

```php
'custom_route' => [
	'title'    => 'Custom Title',
	'path'     => '/custom-route',
	'redirect' => [ $this, 'custom_redirect' ],
	'action'   => [ $this, 'custom_action' ],
],
```

The redirect and action functions can be implemented below the class constructor:

```php
public function custom_redirect() {

	// Perform checks and return the redirect URL.
	if ( is_user_logged_in() ) {
		return hivepress()->router->get_url( 'user_account_page' );
	}

	// Return false to prevent the redirect.
	return false;
}

public function custom_action() {

	// Return the rendered page template.
	return ( new HivePress\Blocks\Template(
		[
			'template' => 'user_login_page',
		]
	) )->render();
}
```

In the same way, you can add URL routes for any pages required for your extension.

#### REST API routes

These routes are used for receiving HTTP requests, performing specific actions and returning a JSON response. For example, when you edit a listing, the form sends a request to the `listing_update_action` route, updating the listing based on the field values.

To define a new REST API route, add an array with the following parameters:

* **path** - the relative route URL path (starts with a slash);
* **base** - the route name to inherit the `path` from;
* **method** - the accepted HTTP method (e.g. `GET`, `POST`);
* **action** - points to the route action function;
* **rest** - flag required for REST API routes.

The code below defines a REST API route with the `/wp-json/hivepress/v1/custom-route` URL, accepts requests via the `POST` method and calls the `custom_action` function.

```php
'custom_route' => [
	'path'   => '/custom-route',
	'method' => 'POST',
	'action' => [ $this, 'custom_action' ],
	'rest'   => true,
],
```

The action function can be implemented below the class constructor:

```php
public function custom_action( $request ) {
	// Perform some actions based on the request.

	if ( ! $request->get_param( 'custom_parameter' ) ) {

		// Return the error code and message.
		return hp\rest_error( 400, 'Custom error message' );
	}

	// Return the response code and data.
	return hp\rest_response(
		200,
		[
			'custom_result' => 123,
		]
	);
}
```

Similarly, you can add REST API routes for any actions required for your extension.

### Customizing routes

You can customize any of the available URL routes via the `hivepress/v1/routes` filter hook. For example, the code below changes the **Add Listing** page URL:

```php
add_filter(
	'hivepress/v1/routes',
	function( $routes ) {
		$routes['listing_submit_page']['path'] = '/custom-slug-here';

		return $routes;
	}
);
```


# Emails

In HivePress, emails are implemented as PHP classes with specific properties, such as the email subject and message with replaceable tokens. For example, the user registration email is implemented as a `User_Register` class, with a subject and message that contains a `%user_name%` token, replaced with a unique user name for each registration.

### Creating emails

If you are developing a HivePress [extension](/developer-docs/tutorials/create-a-custom-hivepress-extension) that requires custom email notifications, you should create a new email. To do this, create a new `class-{email-name}.php` file (use lowercase letters, numbers, and hyphens only) in the `includes/emails` extension subdirectory and HivePress will load it automatically.

Then, define the email PHP class. The class name should be based on the file name but with underscores instead of hyphens and no lowercase restriction (e.g. `Foo_Bar` class for the `class-foo-bar.php` file). Pick a name that is unique enough to avoid conflicts with other HivePress emails.

```php
<?php
namespace HivePress\Emails;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

class Foo_Bar extends Email {
	public function __construct( $args = [] ) {

		// Set the default subject and message.
		$args = hp\merge_arrays(
			[
				'subject' => 'Custom subject',
				'body'    => 'Custom message with a %custom_token%.',
			],
			$args
		);

		parent::__construct( $args );
	}

	public static function init( $meta = [] ) {

		// Add details for the email editor.
		$meta = hp\merge_arrays(
			[
				'label' => 'My Custom Email',
			],
			$meta
		);

		parent::init( $meta );
	}
}

```

The code example above defines a new `Foo_Bar` email with the default subject and message with a replaceable token. It also has a label set in the `init` method, which means that this email is available for editing in the **HivePress > Emails** section.

### Sending emails

To send an email, create its class object with the following parameters:

* **recipient** - the recipient's email address;
* **headers** - an optional array of email headers;
* **tokens** - an array of tokens to replace in the email.

Then, call the `send` method. For example, the code below sends the `Foo_Bar` email to the **<user@example.com>** address, replacing the `%custom_token%` in its content with "123".

```php
( new HivePress\Emails\Foo_Bar(
	[
		'recipient' => 'user@example.com',

		'tokens'    => [
			'custom_token' => 123,
		],
	]
) )->send();
```

### Customizing emails

You can customize any of the available HivePress emails via the `hivepress/v1/emails/{email_name}` filter hook. For example, the code below changes the user registration email subject:

```php
add_filter(
	'hivepress/v1/emails/user_register',
	function( $email ) {
		$email['subject'] = 'Custom subject';

		return $email;
	}
);
```


# Fields

In HivePress, field types are implemented as PHP classes with properties and methods that determine the validation and rendering of the field HTML. For example, the `Email` field type renders a text input that accepts email addresses only. You can use any of the existing field types in forms or even create a new one if required.

### Creating field types

If you are developing a custom HivePress [extension](/developer-docs/tutorials/create-a-custom-hivepress-extension), you may need to create a new field type. To do this, create a new `class-{field-type}.php` file (use lowercase letters, numbers, and hyphens only) in the `includes/fields` extension subdirectory and HivePress will load it automatically.

{% hint style="info" %}
It’s good practice to re-use the existing HivePress field types and avoid creating new ones if possible. For example, a new type may be required if you need a completely new UI element, or if it requires some custom logic.
{% endhint %}

Then, define the field type PHP class. The class name should be based on the file name but with underscores instead of hyphens and no lowercase restriction (e.g. `Foo_Bar` class for the `class-foo-bar.php` file). Pick a name that is unique enough to avoid conflicts with other HivePress field types.

```php
<?php
namespace HivePress\Fields;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

class Foo_Bar extends Field {

	// Declare the field properties.
	protected $max_length;

	// Set the property defaults.
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				'max_length' => 123,
			],
			$args
		);

		parent::__construct( $args );
	}

	// Do something after the field is loaded.
	protected function boot() {
		if ( $this->max_length ) {
			$attributes['maxlength'] = $this->max_length;
		}

		parent::boot();
	}

	// Normalize the field value.
	public function normalize() {
		parent::normalize();

		if ( ! is_null( $this->value ) ) {
			$this->value = trim( wp_unslash( $this->value ) );
		}
	}

	// Sanitize the field value.
	public function sanitize() {
		$this->value = sanitize_text_field( $this->value );
	}

	// Validate the field value.
	public function validate() {
		if ( parent::validate() && strlen( $this->value ) > $this->max_length ) {
			$this->add_errors( 'Custom error message' );
		}

		return ! $this->errors;
	}

	// Render the field HTML.
	public function render() {
		return '<input type="text" name="' . esc_attr( $this->name ) . '" value="' . esc_attr( $this->value ) . '" ' . hp\html_attributes( $this->attributes ) . '>';
	}
}

```

The code example above implements a field type that normalizes values by trimming whitespace and removing slashes, then sanitizes them by removing HTML and special characters, and finally validates values based on its `max_length` property (set to `123` by default). The field itself is rendered as an HTML input of `text` type.

Also, a custom field type can be based on another type's PHP class, allowing you to re-use its properties and methods without repeating the code:

```php
<?php
namespace HivePress\Fields;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

class Foo_Bar extends Number {

	// Override properties and methods of Number type.
}

```

In the same way, you can create custom field types for your HivePress extension.

### Customizing field types

You can customize any of the existing field types using [hooks](https://hivepress.github.io/hook-reference/). For example, the code below changes the default date format for the `Date` field type.

```php
add_filter(
	'hivepress/v1/fields/date',
	function( $field ) {
		$field['display_format'] = 'F j, Y';

		return $field;
	}
);
```

Similarly, you can customize any field type in HivePress or its extensions.


# Checkbox

This field type renders a checkbox.

### Parameters

* **caption** - text displayed next to the checkbox;
* **check\_value** - value considered as "checked" (`true` by default).

### Example

The code below renders a required checkbox field with the `custom_field` name, "Custom field" label and "Custom text" caption.

```php
echo ( new HivePress\Fields\Checkbox(
	[
		'name'     => 'custom_field',
		'label'    => 'Custom field',
		'caption'  => 'Custom text',
		'required' => true,
	]
) )->render();
```


# Checkboxes

This field type renders multiple checkboxes.

### Parameters

Parameters are inherited from the [Select](/developer-docs/framework/fields/select) field type, but `multiple` is always set to `true`.

### Example

The code below renders a field with the `custom_field` name, "Custom field" label and three checkboxes, requiring at least one option to be checked.

```php
echo ( new HivePress\Fields\Checkboxes(
	[
		'name'     => 'custom_field',
		'label'    => 'Custom field',
		'required' => true,

		'options'  => [
			'one'   => 'One',
			'two'   => 'Two',
			'three' => 'Three',
		],
	]
) )->render();
```


# Date

This field type renders a date picker.

### Parameters

* **placeholder** - text that describes the expected value;
* **format** - the source date [format](https://www.php.net/manual/en/datetime.format.php);
* **display\_format** - the displayed date format;
* **min\_date** - the earliest date available for selection;
* **max\_date** - the latest date available for selection;
* **disabled\_dates** - an array of dates disabled in the calendar;
* **disabled\_days** - an array of week day numbers disabled in the calendar;
* **offset** - number of days not available for selection (starting from today);
* **window** - number of days available for selection (starting from today);
* **time** - set to `true` to allow selecting time.

### Example

The code below renders a required date picker with the `custom_field` name, "Custom field" label and custom date formats (e.g. the source date "2001-01-01" is displayed as "January 1, 2001"). It allows selecting a date starting from tomorrow and up to 30 days in advance.

```php
echo ( new HivePress\Fields\Date(
	[
		'name'           => 'custom_field',
		'label'          => 'Custom field',
		'format'         => 'Y-m-d',
		'display_format' => 'F j, Y',
		'offset'         => 1,
		'window'         => 30,
		'required'       => true,
	]
) )->render();
```


# Date Range

This field type renders a date range picker.

### Parameters

Parameters are inherited from the [Date](/developer-docs/framework/fields/date) field type, with these extra ones:

* **min\_length** - the minimum number of days available for selection;
* **max\_length** - the maximum number of days available for selection.

### Example

The code below renders a required date range picker with the `custom_field` name, "Custom field" label and custom date formats (e.g. the source date "2001-01-01" is displayed as "January 1, 2001"). It allows selecting a date range from 7 up to 30 days.

```php
echo ( new HivePress\Fields\Date_Range(
	[
		'name'           => 'custom_field',
		'label'          => 'Custom field',
		'format'         => 'Y-m-d',
		'display_format' => 'F j, Y',
		'min_length'     => 7,
		'max_length'     => 30,
		'required'       => true,
	]
) )->render();
```


# Email

This field type renders an email field.

### Parameters

Parameters are inherited from the [Text](/developer-docs/framework/fields/text) field type, but the values are restricted to emails only.

### Example

The code below renders a required email field with the `custom_field` name, "Custom field" label and "Custom text" placeholder.

```php
echo ( new HivePress\Fields\Email(
	[
		'name'        => 'custom_field',
		'label'       => 'Custom field',
		'placeholder' => 'Custom text',
		'required'    => true,
	]
) )->render();
```


# File

This field type renders a file picker.

### Parameters

* **formats** - an array of the allowed file extensions;
* **multiple** - set to `true` to allow selecting multiple files.

### Example

The code below renders a required file picker with the `custom_field` name and "Custom field" label. It allows selecting multiple JPG, PNG and GIF image files.

```php
echo ( new HivePress\Fields\File(
	[
		'name'     => 'custom_field',
		'label'    => 'Custom field',
		'formats'  => [ 'jpg', 'png', 'gif' ],
		'multiple' => true,
		'required' => true,
	]
) )->render();
```


# Number

This field type renders a number field.

### Parameters

* **placeholder** - text that describes the expected value;
* **decimals** - the number of decimal places;
* **min\_value** - the minimum allowed number;
* **max\_value** - the maximum allowed number.

### Example

The code below renders a required number field with the `custom_field` name, "Custom field" label and "Custom text" placeholder. It allows selecting a number between 1 and 100.

```php
echo ( new HivePress\Fields\Number(
	[
		'name'        => 'custom_field',
		'label'       => 'Custom field',
		'placeholder' => 'Custom text',
		'min_value'   => 1,
		'max_value'   => 100,
		'required'    => true,
	]
) )->render();
```


# Number Range

This field type renders a number range slider.

### Parameters

Parameters are inherited from the [Number](/developer-docs/framework/fields/number) field type. If the `min_value` and `max_value` parameters are set, a range slider is rendered in addition to the number fields.

### Example

The code below renders a required number range slider with the `custom_field` name and "Custom field" label. It allows selecting a number range between 1 and 100.

```php
echo ( new HivePress\Fields\Number_Range(
	[
		'name'      => 'custom_field',
		'label'     => 'Custom field',
		'min_value' => 1,
		'max_value' => 100,
		'required'  => true,
	]
) )->render();
```


# Password

This field type renders a password field.

### Parameters

Parameters are inherited from the [Text](/developer-docs/framework/fields/text) field type, but the entered text is obscured.

### Example

The code below renders a required password field with the `custom_field` name, "Custom field" label and "Custom text" placeholder. The minimum password length is 10 characters.

```php
echo ( new HivePress\Fields\Password(
	[
		'name'        => 'custom_field',
		'label'       => 'Custom field',
		'placeholder' => 'Custom text',
		'min_length'  => 10,
		'required'    => true,
	]
) )->render();
```


# Phone

This field type renders a phone field.

### Parameters

Parameters are inherited from the [Text](/developer-docs/framework/fields/text) field type, with these extra ones:

* **countries** - an array of country codes to restrict the available prefixes;
* **country** - a country code for the default prefix selected.

### Example

The code below renders a required phone field with the `custom_field` name, "Custom field" label and "Custom text" placeholder. It allows the USA and Great Britain phone numbers only, with the USA code selected by default.

```php
echo ( new HivePress\Fields\Phone(
	[
		'name'        => 'custom_field',
		'label'       => 'Custom field',
		'placeholder' => 'Custom text',
		'countries'   => [ 'USA', 'GB' ],
		'country'     => 'USA',
		'required'    => true,
	]
) )->render();
```


# Radio Buttons

This field type renders radio buttons.

### Parameters

Parameters are inherited from the [Select](/developer-docs/framework/fields/select) field type, but `multiple` is always set to `false`.

### Example

The code below renders a field with the `custom_field` name, "Custom field" label and three radio buttons, requiring a single option to be selected.

```php
echo ( new HivePress\Fields\Radio(
	[
		'name'     => 'custom_field',
		'label'    => 'Custom field',
		'required' => true,

		'options'  => [
			'one'   => 'One',
			'two'   => 'Two',
			'three' => 'Three',
		],
	]
) )->render();
```


# Repeater

This field type renders repeatable field groups.

### Parameters

* **fields** - an array of field parameters for a group.

### Example

The code below renders a group with the text and number fields. The text field requires at least 10 characters to be entered, while the minimum required value for the number field is 100. The field group can be repeated using the **Add Item** button.

```php
echo ( new HivePress\Fields\Repeater(
	[
		'name'   => 'custom_field',

		'fields' => [
			'custom_text'   => [
				'placeholder' => 'Custom text',
				'type'        => 'text',
				'min_length'  => 10,
				'_order'      => 123,
			],

			'custom_number' => [
				'placeholder' => 'Custom number',
				'type'        => 'number',
				'min_value'   => 100,
				'_order'      => 321,
			],
		],
	]
) )->render();
```


# Select

This field type renders a drop-down list.

### Parameters

* **placeholder** - text that describes the expected value;
* **options** - an array of options available for selection;
* **multiple** - set to `true` to allow selecting multiple options;
* **max\_values** - the maximum number of selected options;
* **source** - URL of JSON results to populate `options`.

### Example

The code below renders a drop-down list with the `custom_field` name, "Custom field" label and three options, requiring at least one option to be selected.

```php
echo ( new HivePress\Fields\Select(
	[
		'name'     => 'custom_field',
		'label'    => 'Custom field',
		'required' => true,

		'options'  => [
			'one'   => 'One',
			'two'   => 'Two',
			'three' => 'Three',
		],
	]
) )->render();
```


# Text

This field type renders a text field.

### Parameters

* **placeholder** - text that describes the expected value;
* **min\_length** - the minimum allowed text length;
* **max\_length** - the maximum allowed text length;
* **pattern** - [regular expression](https://www.php.net/manual/en/function.preg-match.php) to restrict the allowed text;
* **html** - set to `true` to allow HTML or provide an [array of tags](https://developer.wordpress.org/reference/functions/wp_kses/);
* **readonly** - set to `true` to disallow changing the field value.

### Example

The code below renders a required text field with the `custom_field` name, "Custom field" label and "Custom text" placeholder. The minimum allowed text length is 10 characters.

```php
echo ( new HivePress\Fields\Text(
	[
		'name'        => 'custom_field',
		'label'       => 'Custom field',
		'placeholder' => 'Custom text',
		'min_length'  => 10,
		'required'    => true,
	]
) )->render();
```


# Textarea

This field type renders a multi-line text field.

### Parameters

Parameters are inherited from the [Text](/developer-docs/framework/fields/text) field type, with an extra one:

* **editor** - set to `true` to enable the rich text editor or provide an array of [TinyMCE buttons](https://developer.wordpress.org/reference/functions/wp_editor/).

### Example

The code below renders a required multi-line text field with the `custom_field` name, "Custom field" label and "Custom text" placeholder. The maximum allowed text length is 100 characters.

```php
echo ( new HivePress\Fields\Textarea(
	[
		'name'        => 'custom_field',
		'label'       => 'Custom field',
		'placeholder' => 'Custom text',
		'max_length'  => 100,
		'required'    => true,
	]
) )->render();
```


# Time

This field type renders a time picker.

### Parameters

This type manages time in seconds, so it inherits the [Number](/developer-docs/framework/fields/number) type.

* **display\_format** - the displayed time [format](https://www.php.net/manual/en/datetime.format.php).

### Example

The code below renders a required time picker with the `custom_field` name, "Custom field" label and "Custom text" placeholder. It displays time in a custom format, e.g. "12:00 AM".

```php
echo ( new HivePress\Fields\Time(
	[
		'name'           => 'custom_field',
		'label'          => 'Custom field',
		'placeholder'    => 'Custom text',
		'display_format' => 'g:i A',
		'required'       => true,
	]
) )->render();
```


# URL

This field type renders a URL field.

### Parameters

Parameters are inherited from the [Text](/developer-docs/framework/fields/text) field type, but only URLs are allowed.

### Example

The code below renders a URL field with the `custom_field` name, "Custom field" label and "Custom text" placeholder. This field requires a non-empty value.

```php
echo ( new HivePress\Fields\URL(
	[
		'name'        => 'custom_field',
		'label'       => 'Custom field',
		'placeholder' => 'Custom text',
		'required'    => true,
	]
) )->render();
```


# Forms

In HivePress, forms are implemented as PHP classes with specific properties, such as the form action URL and fields. For example, the `User_Register` form contains fields required for registering a new user and sends requests to the `user_register_action` URL [route](/developer-docs/framework/controllers).

### Creating forms

If you are developing a custom HivePress [extension](/developer-docs/tutorials/create-a-custom-hivepress-extension), you may need to create a new form. To do this, create a new `class-{form-name}.php` file (use lowercase letters, numbers, and hyphens only) in the `includes/forms` extension subdirectory and HivePress will load it automatically.

Then, define the form PHP class. The class name should be based on the file name but with underscores instead of hyphens and no lowercase restriction (e.g. `Foo_Bar` class for the `class-foo-bar.php` file). Pick a name that is unique enough to avoid conflicts with other HivePress forms.

The following form parameters are available:

* **action** - URL for sending requests on submission;
* **method** - the request HTTP method (e.g. `POST`, `GET`);
* **redirect** - `true` to refresh or URL to redirect on success;
* **reset** - `true` to clear the fields on success;
* **description** - text displayed before the form;&#x20;
* **message** - text displayed on success;
* **attributes** - an array of HTML attributes;
* **fields** - an array of [field](/developer-docs/framework/fields) parameters;
* **button** - submit button's parameters.

The code example below implements a form with a single required text field with the `custom_field` name and "Custom field" label. The form's submit button has the "Custom button" label. Once submitted, it sends a request to the `custom_action` URL route via the `POST` method, displaying the "Custom text" message on success and resetting fields.

```php
<?php
namespace HivePress\Forms;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

class Foo_Bar extends Form {
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[

				// Set the form parameters.
				'action'  => hivepress()->router->get_url( 'custom_action' ),
				'method'  => 'POST',
				'message' => 'Custom text',
				'reset'   => true,

				// Set the field parameters.
				'fields'  => [
					'custom_field' => [
						'label'    => 'Custom field',
						'type'     => 'text',
						'required' => true,
						'_order'   => 123,
					],
				],

				// Set the button parameters.
				'button'  => [
					'label' => 'Custom button',
				],
			],
			$args
		);

		parent::__construct( $args );
	}
}

```

Also, a custom form can be based on another form's PHP class, allowing you to re-use its properties and methods without repeating the code:

```php
<?php
namespace HivePress\Forms;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

class Foo_Bar extends Listing_Search {
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				// Override the listing search form parameters.
			],
			$args
		);

		parent::__construct( $args );
	}
}

```

If a form is based on a HivePress [model](/developer-docs/framework/models), you can use the `Model_Form` class to inherit the model fields without repeating them in the form. For example, the code below implements a form based on the `Listing` model with the `title` and `description` fields inherited from it.

```php
<?php
namespace HivePress\Forms;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

class Foo_Bar extends Model_Form {
	public static function init( $meta = [] ) {
		$meta = hp\merge_arrays(
			[
				// Set the model name.
				'model' => 'listing',
			],
			$meta
		);

		parent::init( $meta );
	}

	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				// Set the fields to inherit.
				'fields' => [
					'title'       => [
						'_order' => 123,
					],

					'description' => [
						'_order' => 321,
					],
				],
			],
			$args
		);

		parent::__construct( $args );
	}
}

```

In the same way, you can create custom forms for your HivePress extension.

### Customizing forms

You can customize any of the existing forms using [hooks](https://hivepress.github.io/hook-reference/). For example, the code below makes the **First Name** and **Last Name** fields required in the user profile form:

```php
add_filter(
	'hivepress/v1/forms/user_update',
	function( $form ) {
		$form['fields']['first_name']['required'] = true;
		$form['fields']['last_name']['required'] = true;

		return $form;
	}
);
```

Similarly, you can customize any form in HivePress or its extensions.


# Menus

In HivePress, menus are implemented as PHP classes with specific properties, such as the menu items and HTML attributes. For example, the `User_Account` menu contains the user account menu items, and it's displayed on the account subpages.

### Creating menus

If you are developing a custom HivePress [extension](/developer-docs/tutorials/create-a-custom-hivepress-extension), you may need to create a new menu. To do this, create a new `class-{menu-name}.php` file (use lowercase letters, numbers, and hyphens only) in the `includes/menus` extension subdirectory and HivePress will load it automatically.

Then, define the menu PHP class. The class name should be based on the file name but with underscores instead of hyphens and no lowercase restriction (e.g. `Foo_Bar` class for the `class-foo-bar.php` file). Pick a name that is unique enough to avoid conflicts with other HivePress menus.

The following menu parameters are available:

* **attributes** - an array of HTML attributes;
* **items** - the menu item parameters.

Each menu item is defined with these parameters:

* **label** - menu item label;
* **route** - [route](/developer-docs/framework/controllers) name for linking;
* **url** - custom URL if no `route` is set.

The code example below implements a menu with two items. The first item is based on the `listings_view_page` URL route, so it's linked to the **Listings** page. The second item is linked to a custom URL set in the `url` parameter.

```php
<?php
namespace HivePress\Menus;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

class Foo_Bar extends Menu {
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				// Define the menu items.
				'items' => [
					'first_item'  => [
						'label'  => 'First Item',
						'route'  => 'listings_view_page',
						'_order' => 123,
					],

					'second_item' => [
						'label'  => 'Second Item',
						'url'    => 'https://example.com',
						'_order' => 321,
					],
				],
			],
			$args
		);

		parent::__construct( $args );
	}
}
```

In the same way, you can create custom menus for your HivePress extension.

### Customizing menus

You can customize any of the existing menus using [hooks](https://hivepress.github.io/hook-reference/). For example, the code below adds a new item with the "Custom item" label to the user account menu.

```php
add_filter(
	'hivepress/v1/menus/user_account',
	function( $menu ) {
		$menu['items']['custom_item'] = [
			'label'  => 'Custom item',
			'url'    => 'https://example.com',
			'_order' => 123,
		];

		return $menu;
	}
);
```

Similarly, you can customize any menu in HivePress or its extensions.


# Models

In HivePress, models are PHP classes used as wrappers for WordPress post types, comment types and taxonomies. With models, you can easily create, retrieve, update and delete any database entries in the same way without worrying about data validation and storage.

Let's take a look at the main operations you can perform on models. We use the `Listing` model in the examples below, but the API is the same for all models. You can always check the available models in the `includes/models` subdirectory of HivePress or its extensions.

### Quick example <a href="#bkmrk-quick-example" id="bkmrk-quick-example"></a>

The code example below creates a new listing in the database, then the listing is marked as featured, and finally, the listing is deleted.

```php
// Create listing.
$listing = ( new HivePress\Models\Listing() )->fill(
	[
		'title'       => 'Custom title',
		'description' => 'Custom text',
	]
);

if ( $listing->save( [ 'title', 'description' ] ) ) {

	// Mark as featured.
	$listing->set_featured( true )->save_featured();

	// Delete listing.
	$listing->delete();
}
```

### Creating objects <a href="#bkmrk-creating-objects" id="bkmrk-creating-objects"></a>

To create a model object, create a new instance of the `HivePress\Models\{Model_Name}` class and call the `fill` method with an array of field values. You can check the available fields in the model class file or invoke the `_get_fields` method that returns an array of field objects. The model object will not be saved in the database until you call the `save` method:

```php
// Create listing.
$listing = ( new HivePress\Models\Listing() )->fill(
	[
		'title'    => 'Custom title',
		'featured' => true,
	]
);

// Save listing.
$listing->save();
```

If the object is not saved (e.g. some required fields are empty), you can check the value returned by `save` (instead of calling it separately) and get an array of validation errors:

```php
if ( ! $listing->save() ) {
	$errors = $listing->_get_errors();
}
```

After the object is saved in the database, it gets a unique ID:

```php
$id = $listing->get_id();
```

### Retrieving objects <a href="#bkmrk-retrieving-objects" id="bkmrk-retrieving-objects"></a>

To retrieve a model object from the database, simply query it by ID. Please refer to the [Making Queries](/developer-docs/framework/models/making-queries) page if you want to retrieve multiple objects at once.

```php
$listing = HivePress\Models\Listing::query()->get_by_id( 123 );

if ( $listing ) {
	// Listing exists.
}
```

After you retrieve an object, you can get any field value by calling the `get_{field_name}` method:

```php
$title = $listing->get_title();
```

Also, you can use `has_{field_name}` and `is_{field_name}` aliases for better code readability:

```php
if ( $listing->has_title() && $listing->is_featured() ) {
	// Do something.
}
```

To get the formatted field value, use the `display_{field_name}` method instead:

```php
echo $listing->display_description();
```

If a field references another model object, you can access its field values via the double underscore:

```php
$name = $listing->get_vendor__name();
```

To get an array of all the field values, use the `serialize` method:

```php
$values = $listing->serialize();
```

### Updating objects <a href="#bkmrk-updating-objects" id="bkmrk-updating-objects"></a>

Call the `set_{field_name}` method to set the field value:

```php
$listing->set_title( 'Custom title' );
```

If a field references another model object, you can use its ID as a value:

```php
$listing->set_vendor( 123 );
```

To set multiple fields at once, call the `fill` method with an array of values:

```php
$listing->fill(
	[
		'title'    => 'Custom title',
		'featured' => true,
	]
);
```

There are a few ways to save changes in the database. You can call the `save` method without arguments to save all fields, pass an array of field names to save, or save a single field by calling the `save_{field_name}` method:

```php
// Save all fields.
$listing->save();

// Save specific fields.
$listing->save( [ 'title', 'featured' ] );

// Save a single field.
$listing->save_title();
```

Also, you can chain methods for better code readability:

```php
$listing->set_title( 'example' )->save_title();
```

### Deleting objects <a href="#bkmrk-deleting-objects" id="bkmrk-deleting-objects"></a>

To delete a model object from the database, call the `delete` method:

```php
$listing->delete();
```

Check the method result if you want to make sure that the object is deleted:

```php
if ( ! $listing->delete() ) {
	// Listing can't be deleted.
}
```

Also, models that inherit the `Post` or `Comment` classes can be moved to **Trash**:

```php
$listing->trash();
```


# Making queries

With queries, you can easily filter, retrieve and delete multiple entries from the database. To keep things simple, HivePress implements a common API for all the WordPress query types (e.g. `WP_Query`, `WP_Comment_Query`, `WP_Term_Query`).

Let's take a look at the main query operations. We use the `Listing` model in the examples below, but the API is the same for all models. You can always check the available models in the `includes/models` subdirectory of HivePress or its extensions.

### Quick example <a href="#bkmrk-quick-example" id="bkmrk-quick-example"></a>

The code example below gets three featured listings from a specific category and orders them by the creation date. Then each listing is updated with the "Custom text" title, and finally, all listings are deleted.

```php
// Query listings.
$listings = HivePress\Models\Listing::query()->filter(
	[
		'featured'       => true,
		'categories__in' => [ 123 ],
	]
)->order( [ 'created_date' => 'desc' ] )
->limit( 3 )
->get();

// Update listings.
foreach ( $listings as $listing ) {
	$listing->set_title( 'Custom text' )->save_title();
}

// Delete listings.
$listings->delete();
```

### Creating queries <a href="#bkmrk-creating-queries" id="bkmrk-creating-queries"></a>

To create a query object, call the static `query` method on the `HivePress\Models\{Model_Name}` class:

```php
$query = HivePress\Models\Listing::query();
```

After the object is created, you can start building the query. Please note that the database will not be queried until you call one of the methods for retrieving results.

### Filtering results <a href="#bkmrk-filtering-results" id="bkmrk-filtering-results"></a>

For filtering the results, call the `filter` method with an array where keys are field names and values are used for comparison:

```php
$query->filter(
	[
		'status'   => 'publish',
		'verified' => true,
	]
);
```

The default comparison operator is `=`, but the following operators are also available:

* `not` (!=)
* `gt` (>)
* `gte` (>=)
* `lt` (<)
* `lte` (<=)
* `like`
* `not_like`
* `in`
* `not_in`
* `between`
* `not_between`
* `exists`
* `not_exists`

To use the comparison operator, add it to the field name via the double underscore:

```php
// Filter listings by ID.
$query->filter(
	[
		'id__in' => [ 1, 2, 3 ],
	]
);

// Filter listings with rating > 3.
$query->filter(
	[
		'rating__gt' => 3,
	]
);

// Filter listings with an image.
$query->filter(
	[
		'image__exists' => true,
	]
);
```

Also, you can use the `search` method to search results. For example, the code below searches listings with the "foobar" word in the listing title or description:

```php
$query->search( 'foobar' );
```

Since HivePress doesn't fully replace the WordPress query API, you may need to set some WordPress-level arguments depending on the underlying query type (e.g. `WP_Query`). To do this, simply call the `set_args` method with an array of the WordPress-level query arguments:

```php
$query->set_args(
	[
		'meta_key'   => 'custom_field',
		'meta_value' => 123,
	]
);
```

Similarly, if you build a query object with the HivePress API, you can get an array of WordPress-level query arguments using the `get_args` method (e.g. for passing to `WP_Query`):

```php
$args = $query->get_args();
```

### Ordering results <a href="#bkmrk-ordering-results" id="bkmrk-ordering-results"></a>

To order the query results, call the `order` method with an array where keys are field names and values define the sorting order (`asc` for ascending, `desc` for descending):

```php
$query->order( [ 'created_date' => 'desc' ] );
```

If you filter results by ID and want them to follow the same order, pass the `id__in` argument:

```php
$query->order( 'id__in' );
```

Also, if the query model inherits the `Post` class, you can set a random order:

```php
$query->order( 'random' );
```

### Limiting results <a href="#bkmrk-limiting-results" id="bkmrk-limiting-results"></a>

To limit the number of results, use the `limit` method:

```php
$query->limit( 123 );
```

For retrieving results starting from a specific position, call the `offset` method:

```php
$query->offset( 123 );
```

To paginate results based on the limit, use the `paginate` method with the page number:

```php
$query->paginate( 123 );
```

### Retrieving results <a href="#bkmrk-retrieving-results" id="bkmrk-retrieving-results"></a>

Once the query is ready, call the `get` method to retrieve entries from the database:

```php
$listings = $query->get();
```

It returns an array-like object that you can iterate over (e.g. with `foreach` loop) or check the result count using the `count` method. If you need a regular array, call the `serialize` method:

```php
$listings = $listings->serialize();
```

Also, you can chain query methods for better code readability:

```php
$listings = $query->get()->serialize();
```

To get the IDs only, use the `get_ids` method instead:

```php
$listing_ids = $query->get_ids();
```

For the first result only, call the `get_first` method:

```php
$listing = $query->get_first();
```

To get its ID only, use the `get_first_id` method:

```php
$listing_id = $query->get_first_id();
```

To get the result count only, call the `get_count` method instead:

```php
$listing_count = $query->get_count();
```

For retrieving a single result by ID, call the `get_by_id` method directly:

```php
$listing = HivePress\Models\Listing::query()->get_by_id( 123 );
```

### Updating results <a href="#bkmrk-updating-results" id="bkmrk-updating-results"></a>

Currently, there's no method for updating results in bulk, but you can iterate over the model objects and update them separately:

```php
foreach ( $listings as $listing ) {
	$listing->fill(
		[
			'title'    => 'Custom title',
			'featured' => true,
		]
	)->save( [ 'title', 'featured' ] );
}
```

### Deleting results <a href="#bkmrk-deleting-results" id="bkmrk-deleting-results"></a>

To delete the query results from the database, call the `delete` method:

```php
$query->delete();
```

Also, if the query model inherits the `Post` or `Comment` classes, you can move results to **Trash**:

```php
$query->trash();
```

For deleting a single result without retrieving it, call the `delete_by_id` method directly:

```php
HivePress\Models\Listing::query()->delete_by_id( 123 );
```


# Creating models

If you are developing a custom HivePress [extension](/developer-docs/tutorials/create-a-custom-hivepress-extension), you may need to create a new model. To do this, create a new `class-{model-name}.php` file (use lowercase letters, numbers, and hyphens only) in the `includes/models` extension subdirectory and HivePress will load it automatically.

Then, define the model PHP class. The class name should be based on the file name but with underscores instead of hyphens and no lowercase restriction (e.g. `Foo_Bar` class for the `class-foo-bar.php` file). Pick a name that is unique enough to avoid conflicts with other HivePress models.

```php
<?php
namespace HivePress\Models;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

class Foo_Bar extends Post {
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				// Define the model fields.
				'fields' => [
					'description' => [
						'type'       => 'textarea',
						'max_length' => 1234,
						'required'   => true,
						'_alias'     => 'post_content',
					],
				],
			],
			$args
		);

		parent::__construct( $args );
	}

	// Implement custom methods.
	public function get_short_description() {
		return substr( $this->get_description(), 0, 123 );
	}
}
```

The code example above implements a `Foo_Bar` model based on the `Post` model. This means that the `Foo_Bar` objects are stored as posts of a custom `hp_foo_bar` type (the same `hp_` prefix is used for comment types and taxonomies). A model class can be based on one of the base classes or any other model class. The following base classes are available:

* **Post** - for models based on a custom post type (e.g. `Listing`);
* **Term** - for models based on a custom taxonomy (e.g. `Listing_Category`);
* **Comment** - for models based on a custom comment type (e.g. `Review`).

{% hint style="info" %}
If your model inherits the `Post` class, you must register the `hp_{model_name}` post type separately, either via the [WordPress API](https://developer.wordpress.org/reference/functions/register_post_type/) or [HivePress configuration](/developer-docs/framework/configurations/post-types). Similarly, if your model inherits the `Term` class, you must register a taxonomy.
{% endhint %}

The `Foo_Bar` model has a single required `description` [field](/developer-docs/framework/fields) mapped to the standard `post_content` field (so the value will be stored in the `post_content` column of the `wp_posts` database table). Also, there's a `get_short_description` method used as a helper for getting the short description limited to 123 characters.

In the same way, you can create custom models for your HivePress extension.


# Customizing models

You can customize any of the existing models using [hooks](https://hivepress.github.io/hook-reference/). For example, the code below changes the maximum allowed listing title length and makes the description optional:

```php
add_filter(
    'hivepress/v1/models/listing',
    function( $model ) {
        $model['fields']['title']['max_length'] = 123;
        $model['fields']['description']['required'] = false;

        return $model;        
    }
);
```

Similarly, you can customize any model in HivePress or its extensions.


# Templates

In HivePress, templates are implemented as PHP classes that define arrays of [blocks](/developer-docs/framework/blocks). With blocks, it’s easy to re-use and customize specific layout parts without affecting the whole template. You can always check the available templates in the `includes/templates` subdirectory of HivePress or its extensions.

### Creating templates

If you are developing a custom HivePress [extension](/developer-docs/tutorials/create-a-custom-hivepress-extension), you may need to create a new template. To do this, create a new `class-{template-name}.php` file (use lowercase letters, numbers, and hyphens only) in the `includes/templates` extension subdirectory and HivePress will load it automatically.

Then, define the template PHP class. The class name should be based on the file name but with underscores instead of hyphens and no lowercase restriction (e.g. `Foo_Bar` class for the `class-foo-bar.php` file). Pick a name that is unique enough to avoid conflicts with other HivePress templates.

```php
<?php
namespace HivePress\Templates;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

class Foo_Bar extends Page_Wide {
	public function __construct( $args = [] ) {
		$args = hp\merge_trees(
			[
				'blocks' => [
					'page_content' => [
						'blocks' => [
							'listing_search_form' => [
								'type'   => 'listing_search_form',
								'_order' => 123,
							],

							'listings'            => [
								'type'    => 'listings',
								'number'  => 9,
								'columns' => 3,
								'_order'  => 321,
							],
						],
					],
				],
			],
			$args
		);

		parent::__construct( $args );
	}
}

```

The code example above implements a template based on the `Page_Wide` template and adds the **Listing Search Form** along with the **Listings** block that displays 9 recent listings in 3 columns. These blocks are added to the `page_content` block inherited from the `Page_Wide` template.

In the same way, you can create custom templates for your HivePress extension.

### Customizing templates

You can customize any of the existing templates using [hooks](https://hivepress.github.io/hook-reference/). For example, the code below changes the number of columns on the **Listings** page to 3.

```php
add_filter(
	'hivepress/v1/templates/listings_view_page',
	function( $template ) {
		return hivepress()->helper->merge_trees(
			$template,
			[
				'blocks' => [
					'listings' => [
						'columns' => 3,
					],
				],
			]
		);
	}
);
```

Similarly, you can customize any template in HivePress or its extensions.


