diff --git a/lib/Drupal/chessboard/Plugin/Field/FieldFormatter/ChessboardSimpleFormatter.php b/lib/Drupal/chessboard/Plugin/Field/FieldFormatter/ChessboardSimpleFormatter.php
new file mode 100644
index 0000000..a89ade6
--- /dev/null
+++ b/lib/Drupal/chessboard/Plugin/Field/FieldFormatter/ChessboardSimpleFormatter.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\chessboard\Plugin\Field\FieldFormatter\ChessboardSimpleFormatter.
+ */
+
+namespace Drupal\chessboard\Plugin\Field\FieldFormatter;
+
+use Drupal\Component\Utility\String;
+use Drupal\Core\Field\FormatterBase;
+use Drupal\Core\Field\FieldItemListInterface;
+
+/**
+ * Plugin implementation of the 'chessboard simple' formatter.
+ *
+ * @FieldFormatter(
+ *   id = "chessboard_simple",
+ *   label = @Translation("Chessboard simple"),
+ *   field_types = {
+ *     "chessboard"
+ *   }
+ * )
+ */
+class ChessboardSimpleFormatter extends FormatterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function viewElements(FieldItemListInterface $items) {
+    $elements = array();
+
+    foreach ($items as $delta => $item) {
+      $elements[$delta] = array('#markup' => '<pre>' . String::checkPlain(chunk_split($item->piece_placement, 8, "\n")) . '</pre>');
+    }
+
+    return $elements;
+  }
+
+}
diff --git a/lib/Drupal/chessboard/Plugin/Field/FieldType/ChessboardItem.php b/lib/Drupal/chessboard/Plugin/Field/FieldType/ChessboardItem.php
new file mode 100644
index 0000000..8839dea
--- /dev/null
+++ b/lib/Drupal/chessboard/Plugin/Field/FieldType/ChessboardItem.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\chessboard\Plugin\Field\FieldType\ChessboardItem.
+ */
+
+namespace Drupal\chessboard\Plugin\Field\FieldType;
+
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemBase;
+use Drupal\Core\TypedData\DataDefinition;
+
+/**
+ * Defines the 'chessboard' entity field type.
+ *
+ * @FieldType(
+ *   id = "chessboard",
+ *   label = @Translation("Chessboard"),
+ *   description = @Translation("An entity field containing a chessboard."),
+ *   default_widget = "chessboard_default",
+ *   default_formatter = "chessboard_simple"
+ * )
+ */
+class ChessboardItem extends FieldItemBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function propertyDefinitions(FieldDefinitionInterface $field_definition) {
+    $properties['piece_placement'] = DataDefinition::create('string')
+      ->setLabel(t('Piece placement'));
+
+    return $properties;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function schema(FieldDefinitionInterface $field_definition) {
+    return array(
+      'columns' => array(
+        'piece_placement' => array(
+          'type' => 'char',
+          'length' => 64,
+          'not null' => FALSE,
+        ),
+      ),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConstraints() {
+    $constraints = parent::getConstraints();
+    $constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager();
+    $constraints[] = $constraint_manager->create('ComplexData', array(
+      'piece_placement' => array(
+        'Length' => array(
+          'max' => 64,
+          'min' => 64,
+          'exactMessage' => t('%name: must reference 64 squares.', array('%name' => $this->getFieldDefinition()->getLabel())),
+        ),
+        'ChessboardRegex' => array(
+          'pattern' => '@[^-KQBNRPkqbnrp]@',
+          'match' => FALSE,
+          'message' => t('%name: only standard chess pieces allowed.', array('%name' => $this->getFieldDefinition()->getLabel())),
+        ),
+      ),
+      ));
+
+    return $constraints;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEmpty() {
+    $value = $this->get('piece_placement')->getValue();
+    return $value === NULL || $value === '';
+  }
+}
diff --git a/lib/Drupal/chessboard/Plugin/Field/FieldWidget/ChessboardWidget.php b/lib/Drupal/chessboard/Plugin/Field/FieldWidget/ChessboardWidget.php
new file mode 100644
index 0000000..4af3de7
--- /dev/null
+++ b/lib/Drupal/chessboard/Plugin/Field/FieldWidget/ChessboardWidget.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\chessboard\Plugin\Field\FieldWidget\ChessboardWidget.
+ */
+
+namespace Drupal\chessboard\Plugin\Field\FieldWidget;
+
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\WidgetBase;
+
+/**
+ * Plugin implementation of the 'chessboard' widget.
+ *
+ * @FieldWidget(
+ *   id = "chessboard_default",
+ *   label = @Translation("Chessboard"),
+ *   field_types = {
+ *     "chessboard"
+ *   }
+ * )
+ */
+class ChessboardWidget extends WidgetBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, array &$form_state) {
+    $element['piece_placement'] = $element + array(
+      '#type' => 'textfield',
+      '#default_value' => isset($items[$delta]->piece_placement) ? $items[$delta]->piece_placement : NULL,
+      '#size' => 64,
+      '#maxlength' => 64,
+    );
+
+    return $element;
+  }
+
+}
diff --git a/lib/Drupal/chessboard/Plugin/Validation/Constraint/RegexConstraint.php b/lib/Drupal/chessboard/Plugin/Validation/Constraint/RegexConstraint.php
new file mode 100644
index 0000000..12794e8
--- /dev/null
+++ b/lib/Drupal/chessboard/Plugin/Validation/Constraint/RegexConstraint.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\chessboard\Plugin\Validation\Constraint\RegexConstraint.
+ */
+
+namespace Drupal\chessboard\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraints\Regex;
+
+/**
+ * Regex constraint.
+ *
+ * @Plugin(
+ *   id = "ChessboardRegex",
+ *   label = @Translation("Regex", context = "Validation"),
+ *   type = { "string" }
+ * )
+ */
+class RegexConstraint extends Regex {
+
+  /**
+   * Overrides Regex::validatedBy().
+   */
+  public function validatedBy() {
+    return '\Symfony\Component\Validator\Constraints\RegexValidator';
+  }
+}
