diff --git a/web/app/mu-plugins/nebula-core/includes/classes/Contracts/PostType.php b/web/app/mu-plugins/nebula-core/includes/classes/Contracts/PostType.php
index 96d17f4..b80b20a 100644
--- a/web/app/mu-plugins/nebula-core/includes/classes/Contracts/PostType.php
+++ b/web/app/mu-plugins/nebula-core/includes/classes/Contracts/PostType.php
@@ -53,70 +53,142 @@
  * @return PostType
  */
 abstract class PostType implements Bootable {
+
 	/**
 	 * Post type name.
 	 *
 	 * @var string
 	 */
-	protected $post_type;
+	protected string $post_type;
 
 	/**
-	 * Post type arguments.
+	 * Boot method from Bootable interface
 	 *
-	 * @var array
+	 * @return void
 	 */
-	protected $args = [];
+	public function boot(): void {
+		add_action( 'init', [ $this, 'register_post_type' ] );
+		add_action( 'init', [ $this, 'register_post_type_meta' ] );
+		add_action( 'rest_api_init', [ $this, 'register_post_type_rest_routes' ] );
+	}
 
 	/**
-	 * Post type names.
+	 * Post types can be booted by default.
 	 *
-	 * @var array
+	 * @return bool
 	 */
-	protected $names = [];
+	public function can_boot(): bool {
+		return true;
+	}
 
 	/**
-	 * Boots the class by running `add_action()` and `add_filter()` calls.
+	 * Get post type name.
 	 *
-	 * @return void
+	 * @return string
 	 */
-	public function boot(): void {
-		add_action( 'init', [ $this, 'register' ] );
+	abstract public function get_name(): string;
+
+	/**
+	 * Get post type singular label.
+	 *
+	 * @return string
+	 */
+	abstract public function get_singular_label(): string;
+
+	/**
+	 * Get post type plural label.
+	 *
+	 * @return string
+	 */
+	abstract public function get_plural_label(): string;
+
+	/**
+	 * Get post type menu icon.
+	 *
+	 * @return string
+	 */
+	abstract public function get_menu_icon(): string;
+
+	/**
+	 * Editor supports.
+	 *
+	 * @return array
+	 */
+	abstract public function get_editor_supports(): array;
+
+	/**
+	 * Post type options configuration.
+	 *
+	 * @return array
+	 */
+	protected function get_options(): array {
+		return [
+			'menu_icon'    => $this->get_menu_icon(),
+			'supports'     => $this->get_editor_supports(),
+			'show_in_rest' => true,
+			'public'       => true,
+		];
 	}
 
 	/**
-	 * Determines if the class can be booted.
+	 * Post type labels configuration.
 	 *
-	 * @return bool
+	 * @return array
 	 */
-	public function can_boot(): bool {
-		return true;
+	protected function get_labels(): array {
+		return [
+			'singular' => $this->get_singular_label(),
+			'plural'   => $this->get_plural_label(),
+			'slug'     => $this->get_name(),
+		];
+	}
+
+	/**
+	 * Register post type using Extended CPTs.
+	 */
+	public function register_post_type(): void {
+		register_extended_post_type(
+			$this->get_name(),
+			$this->get_options(),
+			$this->get_labels()
+		);
 	}
 
 	/**
-	 * Validate the post type name format.
+	 * Automatically register meta fields from child class.
+	 */
+	public function register_post_type_meta(): void {
+		foreach ( $this->get_custom_post_meta() as $meta_key => $meta_args ) {
+			register_post_meta( $this->get_name(), $meta_key, $meta_args );
+		}
+	}
+
+	/**
+	 * Register REST routes.
 	 *
-	 * @param string $post_type The post type name.
 	 * @return void
-	 * @throws Exception If the taxonomy name is invalid.
 	 */
-	protected function validate( string $post_type ): void {
-		if ( ! preg_match( '/^[a-z_]+$/', $post_type ) ) {
-			throw new Exception( 'Invalid post type name: ' . esc_html( $post_type ) . '. Must be lowercase, contain no spaces or hyphens, and underscores between words.' );
+	public function register_post_type_rest_routes(): void {
+		foreach ( $this->get_custom_rest_routes() as $route ) {
+			register_rest_route( $route['namespace'], $route['route'], $route['args'] );
 		}
 	}
 
 	/**
-	 * Register the post type.
+	 * Get custom post meta.
 	 *
-	 * @return void
+	 * @return array
 	 */
-	public function register(): void {
-		$this->validate( $this->post_type );
+	public function get_custom_post_meta(): array {
+		return [];
+	}
 
-		register_extended_post_type(
-			$this->post_type,
-			$this->args,
-			$this->names
-		);
+	/**
+	 * Child class can override this to provide REST route definitions.
+	 *
+	 * @return array
+	 */
+	public function get_custom_rest_routes(): array {
+		return [];
 	}
 }