diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php
index a0b10e613a3..ff0bffbf856 100644
--- a/features/bootstrap/FeatureContext.php
+++ b/features/bootstrap/FeatureContext.php
@@ -298,6 +298,7 @@ public function thereIsAFileConfigDummyObject()
     {
         $fileConfigDummy = new FileConfigDummy();
         $fileConfigDummy->setName('ConfigDummy');
+        $fileConfigDummy->setFoo('Foo');
 
         $this->manager->persist($fileConfigDummy);
         $this->manager->flush();
diff --git a/features/configurable.feature b/features/configurable.feature
index b08a9de7b01..44662044c1c 100644
--- a/features/configurable.feature
+++ b/features/configurable.feature
@@ -19,6 +19,7 @@ Feature: Configurable resource CRUD
           {
               "@id": "/fileconfigdummies/1",
               "@type": "fileconfigdummy",
+              "foo": "Foo",
               "id": 1,
               "name": "ConfigDummy"
           }
@@ -55,6 +56,7 @@ Feature: Configurable resource CRUD
           "@context": "\/contexts\/fileconfigdummy",
           "@id": "\/fileconfigdummies\/1",
           "@type": "fileconfigdummy",
+          "foo": "Foo",
           "id": 1,
           "name": "ConfigDummy"
       }
diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
index d6b0f7489f3..2a7690797ed 100644
--- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
+++ b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
@@ -255,8 +255,14 @@ private function registerLoaders(ContainerBuilder $container, array $bundles)
         $container->getDefinition('api_platform.metadata.resource.name_collection_factory.yaml')->replaceArgument(0, $yamlResources);
         $container->getDefinition('api_platform.metadata.resource.metadata_factory.yaml')->replaceArgument(0, $yamlResources);
 
+        $container->getDefinition('api_platform.metadata.property.name_collection_factory.yaml')->replaceArgument(0, $yamlResources);
+        $container->getDefinition('api_platform.metadata.property.metadata_factory.yaml')->replaceArgument(0, $yamlResources);
+
         $container->getDefinition('api_platform.metadata.resource.name_collection_factory.xml')->replaceArgument(0, $xmlResources);
         $container->getDefinition('api_platform.metadata.resource.metadata_factory.xml')->replaceArgument(0, $xmlResources);
+
+        $container->getDefinition('api_platform.metadata.property.name_collection_factory.xml')->replaceArgument(0, $xmlResources);
+        $container->getDefinition('api_platform.metadata.property.metadata_factory.xml')->replaceArgument(0, $xmlResources);
     }
 
     /**
diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata.xml b/src/Bridge/Symfony/Bundle/Resources/config/metadata.xml
index 97d9dee2396..11b9961536b 100644
--- a/src/Bridge/Symfony/Bundle/Resources/config/metadata.xml
+++ b/src/Bridge/Symfony/Bundle/Resources/config/metadata.xml
@@ -89,6 +89,16 @@
             <argument type="service" id="api_platform.metadata.property.name_collection_factory.cached.inner" />
         </service>
 
+        <service id="api_platform.metadata.property.name_collection_factory.yaml" class="ApiPlatform\Core\Metadata\Property\Factory\YamlPropertyNameCollectionFactory" decorates="api_platform.metadata.property.name_collection_factory" public="false">
+            <argument type="collection" />
+            <argument type="service" id="api_platform.metadata.property.name_collection_factory.yaml.inner" />
+        </service>
+
+        <service id="api_platform.metadata.property.name_collection_factory.xml" class="ApiPlatform\Core\Metadata\Property\Factory\XmlPropertyNameCollectionFactory" decorates="api_platform.metadata.property.name_collection_factory" public="false">
+            <argument type="collection" />
+            <argument type="service" id="api_platform.metadata.property.name_collection_factory.xml.inner" />
+        </service>
+
         <!-- Property metadata -->
 
         <service id="api_platform.metadata.property.metadata_factory" alias="api_platform.metadata.property.metadata_factory.annotation" />
@@ -108,6 +118,16 @@
             <argument type="service" id="api_platform.metadata.property.metadata_factory.inherited.inner" />
         </service>
 
+        <service id="api_platform.metadata.property.metadata_factory.yaml" class="ApiPlatform\Core\Metadata\Property\Factory\YamlPropertyMetadataFactory" decorates="api_platform.metadata.property.metadata_factory" decoration-priority="40" public="false">
+            <argument type="collection" />
+            <argument type="service" id="api_platform.metadata.property.metadata_factory.yaml.inner" />
+        </service>
+
+        <service id="api_platform.metadata.property.metadata_factory.xml" class="ApiPlatform\Core\Metadata\Property\Factory\XmlPropertyMetadataFactory" decorates="api_platform.metadata.property.metadata_factory" decoration-priority="40" public="false">
+            <argument type="collection" />
+            <argument type="service" id="api_platform.metadata.property.metadata_factory.xml.inner" />
+        </service>
+
         <service id="api_platform.metadata.property.metadata_factory.serializer" class="ApiPlatform\Core\Metadata\Property\Factory\SerializerPropertyMetadataFactory" decorates="api_platform.metadata.property.metadata_factory" decoration-priority="30" public="false">
             <argument type="service" id="api_platform.metadata.resource.metadata_factory" />
             <argument type="service" id="serializer.mapping.class_metadata_factory" />
diff --git a/src/Metadata/Property/Factory/XmlPropertyMetadataFactory.php b/src/Metadata/Property/Factory/XmlPropertyMetadataFactory.php
new file mode 100644
index 00000000000..19d79b0def9
--- /dev/null
+++ b/src/Metadata/Property/Factory/XmlPropertyMetadataFactory.php
@@ -0,0 +1,202 @@
+<?php
+
+/*
+ * This file is part of the API Platform project.
+ *
+ * (c) Kévin Dunglas <dunglas@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Exception\InvalidArgumentException;
+use ApiPlatform\Core\Exception\PropertyNotFoundException;
+use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
+use Symfony\Component\Config\Util\XmlUtils;
+
+/**
+ * Creates a property metadata from XML {@see Property} configuration.
+ *
+ * @author Baptiste Meyer <baptiste.meyer@gmail.com>
+ */
+class XmlPropertyMetadataFactory implements PropertyMetadataFactoryInterface
+{
+    const RESOURCE_SCHEMA = __DIR__.'/../../schema/metadata.xsd';
+
+    private $paths;
+    private $decorated;
+
+    /**
+     * @param string[]                              $paths
+     * @param PropertyMetadataFactoryInterface|null $decorated
+     */
+    public function __construct(array $paths, PropertyMetadataFactoryInterface $decorated = null)
+    {
+        $this->paths = $paths;
+        $this->decorated = $decorated;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function create(string $resourceClass, string $property, array $options = []) : PropertyMetadata
+    {
+        $parentPropertyMetadata = null;
+        if ($this->decorated) {
+            try {
+                $parentPropertyMetadata = $this->decorated->create($resourceClass, $property, $options);
+            } catch (PropertyNotFoundException $propertyNotFoundException) {
+                // Ignore not found exception from decorated factories
+            }
+        }
+
+        if (
+            !property_exists($resourceClass, $property) ||
+            empty($propertyMetadata = $this->getMetadata($resourceClass, $property))
+        ) {
+            return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property);
+        }
+
+        if ($parentPropertyMetadata) {
+            return $this->update($parentPropertyMetadata, $propertyMetadata);
+        }
+
+        return new PropertyMetadata(
+            null,
+            $propertyMetadata['description'],
+            $propertyMetadata['readable'],
+            $propertyMetadata['writable'],
+            $propertyMetadata['readableLink'],
+            $propertyMetadata['writableLink'],
+            $propertyMetadata['required'],
+            $propertyMetadata['identifier'],
+            $propertyMetadata['iri'],
+            null,
+            $propertyMetadata['attributes']
+        );
+    }
+
+    /**
+     * Returns the metadata from the decorated factory if available or throws an exception.
+     *
+     * @param PropertyMetadata|null $parentPropertyMetadata
+     * @param string                $resourceClass
+     * @param string                $property
+     *
+     * @throws PropertyNotFoundException
+     *
+     * @return PropertyMetadata
+     */
+    private function handleNotFound(PropertyMetadata $parentPropertyMetadata = null, string $resourceClass, string $property) : PropertyMetadata
+    {
+        if ($parentPropertyMetadata) {
+            return $parentPropertyMetadata;
+        }
+
+        throw new PropertyNotFoundException(sprintf('Property "%s" of the resource class "%s" not found.', $property, $resourceClass));
+    }
+
+    /**
+     * Extracts metadata from the XML tree.
+     *
+     * @param string $resourceClass
+     * @param string $propertyName
+     *
+     * @throws InvalidArgumentException
+     *
+     * @return array
+     */
+    private function getMetadata(string $resourceClass, string $propertyName) : array
+    {
+        foreach ($this->paths as $path) {
+            try {
+                $domDocument = XmlUtils::loadFile($path, self::RESOURCE_SCHEMA);
+            } catch (\InvalidArgumentException $e) {
+                throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+            }
+
+            $properties = (new \DOMXPath($domDocument))->query(sprintf('//resources/resource[@class="%s"]/property[@name="%s"]', $resourceClass, $propertyName));
+
+            if (
+                false === $properties ||
+                0 >= $properties->length ||
+                null === $properties->item(0) ||
+                false === $property = simplexml_import_dom($properties->item(0))
+            ) {
+                continue;
+            }
+
+            return [
+                'description' => (string) $property['description'] ?: null,
+                'readable' => $property['readable'] ? (bool) XmlUtils::phpize($property['readable']) : null,
+                'writable' => $property['writable'] ? (bool) XmlUtils::phpize($property['writable']) : null,
+                'readableLink' => $property['readableLink'] ? (bool) XmlUtils::phpize($property['readableLink']) : null,
+                'writableLink' => $property['writableLink'] ? (bool) XmlUtils::phpize($property['writableLink']) : null,
+                'required' => $property['required'] ? (bool) XmlUtils::phpize($property['required']) : null,
+                'identifier' => $property['identifier'] ? (bool) XmlUtils::phpize($property['identifier']) : null,
+                'iri' => (string) $property['iri'] ?: null,
+                'attributes' => $this->getAttributes($property),
+            ];
+        }
+
+        return [];
+    }
+
+    /**
+     * Recursively transforms an attribute structure into an associative array.
+     *
+     * @param \SimpleXMLElement $element
+     *
+     * @return array
+     */
+    private function getAttributes(\SimpleXMLElement $element) : array
+    {
+        $attributes = [];
+        foreach ($element->attribute as $attribute) {
+            $value = isset($attribute->attribute[0]) ? $this->getAttributes($attribute) : (string) $attribute;
+
+            if (isset($attribute['name'])) {
+                $attributes[(string) $attribute['name']] = $value;
+            } else {
+                $attributes[] = $value;
+            }
+        }
+
+        return $attributes;
+    }
+
+    /**
+     * Creates a new instance of metadata if the property is not already set.
+     *
+     * @param PropertyMetadata $propertyMetadata
+     * @param array            $metadata
+     *
+     * @return PropertyMetadata
+     */
+    private function update(PropertyMetadata $propertyMetadata, array $metadata) : PropertyMetadata
+    {
+        $metadataAccessors = [
+            'description' => 'get',
+            'readable' => 'is',
+            'writable' => 'is',
+            'writableLink' => 'is',
+            'readableLink' => 'is',
+            'required' => 'is',
+            'identifier' => 'is',
+            'iri' => 'get',
+            'attributes' => 'get',
+        ];
+
+        foreach ($metadataAccessors as $metadataKey => $accessorPrefix) {
+            if (null === $metadata[$metadataKey] || null !== $propertyMetadata->{$accessorPrefix.ucfirst($metadataKey)}()) {
+                continue;
+            }
+
+            $propertyMetadata = $propertyMetadata->{'with'.ucfirst($metadataKey)}($metadata[$metadataKey]);
+        }
+
+        return $propertyMetadata;
+    }
+}
diff --git a/src/Metadata/Property/Factory/XmlPropertyNameCollectionFactory.php b/src/Metadata/Property/Factory/XmlPropertyNameCollectionFactory.php
new file mode 100644
index 00000000000..995a048b1b4
--- /dev/null
+++ b/src/Metadata/Property/Factory/XmlPropertyNameCollectionFactory.php
@@ -0,0 +1,96 @@
+<?php
+
+/*
+ * This file is part of the API Platform project.
+ *
+ * (c) Kévin Dunglas <dunglas@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Exception\InvalidArgumentException;
+use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
+use ApiPlatform\Core\Metadata\Property\PropertyNameCollection;
+use Symfony\Component\Config\Util\XmlUtils;
+
+/**
+ * Creates a property name collection from XML {@see Property} configuration files.
+ *
+ * @author Baptiste Meyer <baptiste.meyer@gmail.com>
+ */
+class XmlPropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface
+{
+    const RESOURCE_SCHEMA = __DIR__.'/../../schema/metadata.xsd';
+
+    private $paths;
+    private $decorated;
+
+    /**
+     * @param array                                       $paths
+     * @param PropertyNameCollectionFactoryInterface|null $decorated
+     */
+    public function __construct(array $paths, PropertyNameCollectionFactoryInterface $decorated = null)
+    {
+        $this->paths = $paths;
+        $this->decorated = $decorated;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @throws InvalidArgumentException
+     */
+    public function create(string $resourceClass, array $options = []) : PropertyNameCollection
+    {
+        if ($this->decorated) {
+            try {
+                $propertyNameCollection = $this->decorated->create($resourceClass, $options);
+            } catch (ResourceClassNotFoundException $resourceClassNotFoundException) {
+                // Ignore not found exceptions from parent
+            }
+        }
+
+        if (!class_exists($resourceClass)) {
+            if (isset($propertyNameCollection)) {
+                return $propertyNameCollection;
+            }
+
+            throw new ResourceClassNotFoundException(sprintf('The resource class "%s" does not exist.', $resourceClass));
+        }
+
+        $propertyNames = [];
+
+        foreach ($this->paths as $path) {
+            try {
+                $domDocument = XmlUtils::loadFile($path, self::RESOURCE_SCHEMA);
+            } catch (\InvalidArgumentException $e) {
+                throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+            }
+
+            $properties = (new \DOMXPath($domDocument))->query(sprintf('//resources/resource[@class="%s"]/property', $resourceClass));
+
+            if (false === $properties || 0 >= $properties->length) {
+                continue;
+            }
+
+            foreach ($properties as $property) {
+                if ('' === $propertyName = $property->getAttribute('name')) {
+                    continue;
+                }
+
+                $propertyNames[$propertyName] = true;
+            }
+        }
+
+        if (isset($propertyNameCollection)) {
+            foreach ($propertyNameCollection as $propertyName) {
+                $propertyNames[$propertyName] = true;
+            }
+        }
+
+        return new PropertyNameCollection(array_keys($propertyNames));
+    }
+}
diff --git a/src/Metadata/Property/Factory/YamlPropertyMetadataFactory.php b/src/Metadata/Property/Factory/YamlPropertyMetadataFactory.php
new file mode 100644
index 00000000000..bda8e81f794
--- /dev/null
+++ b/src/Metadata/Property/Factory/YamlPropertyMetadataFactory.php
@@ -0,0 +1,214 @@
+<?php
+
+/*
+ * This file is part of the API Platform project.
+ *
+ * (c) Kévin Dunglas <dunglas@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Exception\InvalidArgumentException;
+use ApiPlatform\Core\Exception\PropertyNotFoundException;
+use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
+use Symfony\Component\Yaml\Exception\ParseException;
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * Creates a property metadata from YAML {@see Property} configuration files.
+ *
+ * @author Baptiste Meyer <baptiste.meyer@gmail.com>
+ */
+class YamlPropertyMetadataFactory implements PropertyMetadataFactoryInterface
+{
+    private $paths;
+    private $decorated;
+
+    /**
+     * @param array                                 $paths
+     * @param PropertyMetadataFactoryInterface|null $decorated
+     */
+    public function __construct(array $paths, PropertyMetadataFactoryInterface $decorated = null)
+    {
+        $this->paths = $paths;
+        $this->decorated = $decorated;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function create(string $resourceClass, string $property, array $options = []) : PropertyMetadata
+    {
+        $parentPropertyMetadata = null;
+        if ($this->decorated) {
+            try {
+                $parentPropertyMetadata = $this->decorated->create($resourceClass, $property, $options);
+            } catch (PropertyNotFoundException $propertyNotFoundException) {
+                // Ignore not found exception from decorated factories
+            }
+        }
+
+        if (
+            !property_exists($resourceClass, $property) ||
+            empty($propertyMetadata = $this->getMetadata($resourceClass, $property))
+        ) {
+            return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property);
+        }
+
+        if ($parentPropertyMetadata) {
+            return $this->update($parentPropertyMetadata, $propertyMetadata);
+        }
+
+        return new PropertyMetadata(
+            null,
+            $propertyMetadata['description'],
+            $propertyMetadata['readable'],
+            $propertyMetadata['writable'],
+            $propertyMetadata['readableLink'],
+            $propertyMetadata['writableLink'],
+            $propertyMetadata['required'],
+            $propertyMetadata['identifier'],
+            $propertyMetadata['iri'],
+            null,
+            $propertyMetadata['attributes']
+        );
+    }
+
+    /**
+     * Returns the metadata from the decorated factory if available or throws an exception.
+     *
+     * @param PropertyMetadata|null $parentPropertyMetadata
+     * @param string                $resourceClass
+     * @param string                $property
+     *
+     * @throws PropertyNotFoundException
+     *
+     * @return PropertyMetadata
+     */
+    private function handleNotFound(PropertyMetadata $parentPropertyMetadata = null, string $resourceClass, string $property) : PropertyMetadata
+    {
+        if ($parentPropertyMetadata) {
+            return $parentPropertyMetadata;
+        }
+
+        throw new PropertyNotFoundException(sprintf('Property "%s" of the resource class "%s" not found.', $property, $resourceClass));
+    }
+
+    /**
+     * Extracts metadata from the YAML tree.
+     *
+     * @param string $resourceClass
+     * @param string $property
+     *
+     * @throws ParseException
+     * @throws InvalidArgumentException
+     *
+     * @return array
+     */
+    private function getMetadata(string $resourceClass, string $property) : array
+    {
+        foreach ($this->paths as $path) {
+            try {
+                $resources = Yaml::parse(file_get_contents($path));
+            } catch (ParseException $parseException) {
+                $parseException->setParsedFile($path);
+
+                throw $parseException;
+            }
+
+            if (null === $resources = $resources['resources'] ?? $resources) {
+                continue;
+            }
+
+            if (!is_array($resources)) {
+                throw new InvalidArgumentException(sprintf('"resources" setting is expected to be null or an array, %s given in "%s".', gettype($resources), $path));
+            }
+
+            foreach ($resources as $resourceName => $resource) {
+                if (null === $resource) {
+                    continue;
+                }
+
+                if (!is_array($resource)) {
+                    throw new InvalidArgumentException(sprintf('"%s" setting is expected to be null or an array, %s given in "%s".', $resourceName, gettype($resource), $path));
+                }
+
+                if (!isset($resource['class'])) {
+                    throw new InvalidArgumentException(sprintf('"class" setting is expected to be a string, none given in "%s".', $path));
+                }
+
+                if ($resourceClass !== $resource['class'] || !isset($resource['properties'])) {
+                    continue;
+                }
+
+                if (!is_array($resource['properties'])) {
+                    throw new InvalidArgumentException(sprintf('"properties" setting is expected to be null or an array, %s given in "%s".', gettype($resource['properties']), $path));
+                }
+
+                foreach ($resource['properties'] as $propertyName => $propertyValues) {
+                    if (null === $propertyValues) {
+                        continue;
+                    }
+
+                    if (!is_array($propertyValues)) {
+                        throw new InvalidArgumentException(sprintf('"%s" setting is expected to be null or an array, %s given in "%s".', $propertyName, gettype($propertyValues), $path));
+                    }
+
+                    if ($property !== $propertyName) {
+                        continue;
+                    }
+
+                    return [
+                        'description' => isset($propertyValues['description']) && is_scalar($propertyValues['description']) ? $propertyValues['description'] : null,
+                        'readable' => isset($propertyValues['readable']) && is_bool($propertyValues['readable']) ? $propertyValues['readable'] : null,
+                        'writable' => isset($propertyValues['writable']) && is_bool($propertyValues['writable']) ? $propertyValues['writable'] : null,
+                        'readableLink' => isset($propertyValues['readableLink']) && is_bool($propertyValues['readableLink']) ? $propertyValues['readableLink'] : null,
+                        'writableLink' => isset($propertyValues['writableLink']) && is_bool($propertyValues['writableLink']) ? $propertyValues['writableLink'] : null,
+                        'required' => isset($propertyValues['required']) && is_bool($propertyValues['required']) ? $propertyValues['required'] : null,
+                        'identifier' => isset($propertyValues['identifier']) && is_bool($propertyValues['identifier']) ? $propertyValues['identifier'] : null,
+                        'iri' => isset($propertyValues['iri']) && is_scalar($propertyValues['iri']) ? $propertyValues['iri'] : null,
+                        'attributes' => $propertyValues['attributes'] ?? null,
+                    ];
+                }
+            }
+        }
+
+        return [];
+    }
+
+    /**
+     * Creates a new instance of metadata if the property is not already set.
+     *
+     * @param PropertyMetadata $propertyMetadata
+     * @param array            $metadata
+     *
+     * @return PropertyMetadata
+     */
+    private function update(PropertyMetadata $propertyMetadata, array $metadata) : PropertyMetadata
+    {
+        $metadataAccessors = [
+            'description' => 'get',
+            'readable' => 'is',
+            'writable' => 'is',
+            'writableLink' => 'is',
+            'readableLink' => 'is',
+            'required' => 'is',
+            'identifier' => 'is',
+            'iri' => 'get',
+            'attributes' => 'get',
+        ];
+
+        foreach ($metadataAccessors as $metadataKey => $accessorPrefix) {
+            if (null === $metadata[$metadataKey] || null !== $propertyMetadata->{$accessorPrefix.ucfirst($metadataKey)}()) {
+                continue;
+            }
+
+            $propertyMetadata = $propertyMetadata->{'with'.ucfirst($metadataKey)}($metadata[$metadataKey]);
+        }
+
+        return $propertyMetadata;
+    }
+}
diff --git a/src/Metadata/Property/Factory/YamlPropertyNameCollectionFactory.php b/src/Metadata/Property/Factory/YamlPropertyNameCollectionFactory.php
new file mode 100644
index 00000000000..19121b119ce
--- /dev/null
+++ b/src/Metadata/Property/Factory/YamlPropertyNameCollectionFactory.php
@@ -0,0 +1,126 @@
+<?php
+
+/*
+ * This file is part of the API Platform project.
+ *
+ * (c) Kévin Dunglas <dunglas@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Exception\InvalidArgumentException;
+use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
+use ApiPlatform\Core\Metadata\Property\PropertyNameCollection;
+use Symfony\Component\Yaml\Exception\ParseException;
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * Creates a property name collection from YAML {@see Property} configuration files.
+ *
+ * @author Baptiste Meyer <baptiste.meyer@gmail.com>
+ */
+class YamlPropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface
+{
+    private $paths;
+    private $decorated;
+
+    /**
+     * @param array                                       $paths
+     * @param PropertyNameCollectionFactoryInterface|null $decorated
+     */
+    public function __construct(array $paths, PropertyNameCollectionFactoryInterface $decorated = null)
+    {
+        $this->paths = $paths;
+        $this->decorated = $decorated;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @throws ParseException
+     * @throws InvalidArgumentException
+     */
+    public function create(string $resourceClass, array $options = []) : PropertyNameCollection
+    {
+        if ($this->decorated) {
+            try {
+                $propertyNameCollection = $this->decorated->create($resourceClass, $options);
+            } catch (ResourceClassNotFoundException $resourceClassNotFoundException) {
+                // Ignore not found exceptions from parent
+            }
+        }
+
+        if (!class_exists($resourceClass)) {
+            if (isset($propertyNameCollection)) {
+                return $propertyNameCollection;
+            }
+
+            throw new ResourceClassNotFoundException(sprintf('The resource class "%s" does not exist.', $resourceClass));
+        }
+
+        $propertyNames = [];
+
+        foreach ($this->paths as $path) {
+            try {
+                $resources = Yaml::parse(file_get_contents($path));
+            } catch (ParseException $parseException) {
+                $parseException->setParsedFile($path);
+
+                throw $parseException;
+            }
+
+            if (null === $resources = $resources['resources'] ?? $resources) {
+                continue;
+            }
+
+            if (!is_array($resources)) {
+                throw new InvalidArgumentException(sprintf('"resources" setting is expected to be null or an array, %s given in "%s".', gettype($resources), $path));
+            }
+
+            foreach ($resources as $resourceName => $resource) {
+                if (null === $resource) {
+                    continue;
+                }
+
+                if (!is_array($resource)) {
+                    throw new InvalidArgumentException(sprintf('"%s" setting is expected to be null or an array, %s given in "%s".', $resourceName, gettype($resource), $path));
+                }
+
+                if (!isset($resource['class'])) {
+                    throw new InvalidArgumentException(sprintf('"class" setting is expected to be a string, none given in "%s".', $path));
+                }
+
+                if ($resourceClass !== $resource['class'] || !isset($resource['properties'])) {
+                    continue;
+                }
+
+                if (!is_array($resource['properties'])) {
+                    throw new InvalidArgumentException(sprintf('"properties" setting is expected to be null or an array, %s given in "%s".', gettype($resource['properties']), $path));
+                }
+
+                foreach ($resource['properties'] as $propertyName => $propertyValues) {
+                    if (null === $propertyValues) {
+                        continue;
+                    }
+
+                    if (!is_array($propertyValues)) {
+                        throw new InvalidArgumentException(sprintf('"%s" setting is expected to be null or an array, %s given in "%s".', $propertyName, gettype($propertyValues), $path));
+                    }
+
+                    $propertyNames[$propertyName] = true;
+                }
+            }
+        }
+
+        if (isset($propertyNameCollection)) {
+            foreach ($propertyNameCollection as $propertyName) {
+                $propertyNames[$propertyName] = true;
+            }
+        }
+
+        return new PropertyNameCollection(array_keys($propertyNames));
+    }
+}
diff --git a/src/Metadata/Resource/Factory/XmlResourceMetadataFactory.php b/src/Metadata/Resource/Factory/XmlResourceMetadataFactory.php
index 74cb2e04662..f3d7d1157ac 100644
--- a/src/Metadata/Resource/Factory/XmlResourceMetadataFactory.php
+++ b/src/Metadata/Resource/Factory/XmlResourceMetadataFactory.php
@@ -83,9 +83,9 @@ private function getMetadata(string $resourceClass) : array
                 }
 
                 return [
-                    (string) $resource['shortName'] ?? null,
-                    (string) $resource['description'] ?? null,
-                    (string) $resource['iri'] ?? null,
+                    (string) $resource['shortName'] ?: null,
+                    (string) $resource['description'] ?: null,
+                    (string) $resource['iri'] ?: null,
                     $this->getAttributes($resource, 'itemOperation') ?: null,
                     $this->getAttributes($resource, 'collectionOperation') ?: null,
                     $this->getAttributes($resource, 'attribute') ?: null,
diff --git a/src/Metadata/Resource/Factory/YamlResourceMetadataFactory.php b/src/Metadata/Resource/Factory/YamlResourceMetadataFactory.php
index 22ad3d70eb9..dfcc21979ec 100644
--- a/src/Metadata/Resource/Factory/YamlResourceMetadataFactory.php
+++ b/src/Metadata/Resource/Factory/YamlResourceMetadataFactory.php
@@ -14,7 +14,8 @@
 use ApiPlatform\Core\Exception\InvalidArgumentException;
 use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
 use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
-use Symfony\Component\Yaml\Parser as YamlParser;
+use Symfony\Component\Yaml\Exception\ParseException;
+use Symfony\Component\Yaml\Yaml;
 
 /**
  * Creates a resource metadata from yml {@see Resource} configuration.
@@ -23,7 +24,6 @@
  */
 final class YamlResourceMetadataFactory implements ResourceMetadataFactoryInterface
 {
-    private $yamlParser;
     private $decorated;
     private $paths;
 
@@ -33,13 +33,14 @@ final class YamlResourceMetadataFactory implements ResourceMetadataFactoryInterf
      */
     public function __construct(array $paths, ResourceMetadataFactoryInterface $decorated = null)
     {
-        $this->yamlParser = new YamlParser();
         $this->paths = $paths;
         $this->decorated = $decorated;
     }
 
     /**
      * {@inheritdoc}
+     *
+     * @throws ParseException
      */
     public function create(string $resourceClass) : ResourceMetadata
     {
@@ -61,7 +62,14 @@ public function create(string $resourceClass) : ResourceMetadata
         $metadata = null;
 
         foreach ($this->paths as $path) {
-            $resources = $this->yamlParser->parse(file_get_contents($path));
+            try {
+                $resources = Yaml::parse(file_get_contents($path));
+            } catch (ParseException $parseException) {
+                $parseException->setParsedFile($path);
+
+                throw $parseException;
+            }
+
             $resources = $resources['resources'] ?? $resources;
 
             foreach ($resources as $resource) {
diff --git a/src/Metadata/Resource/Factory/YamlResourceNameCollectionFactory.php b/src/Metadata/Resource/Factory/YamlResourceNameCollectionFactory.php
index 490220a8dfe..909ce85e3a1 100644
--- a/src/Metadata/Resource/Factory/YamlResourceNameCollectionFactory.php
+++ b/src/Metadata/Resource/Factory/YamlResourceNameCollectionFactory.php
@@ -13,7 +13,8 @@
 
 use ApiPlatform\Core\Exception\InvalidArgumentException;
 use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection;
-use Symfony\Component\Yaml\Parser as YamlParser;
+use Symfony\Component\Yaml\Exception\ParseException;
+use Symfony\Component\Yaml\Yaml;
 
 /**
  * Creates a resource name collection from {@see Resource} configuration files.
@@ -22,7 +23,6 @@
  */
 final class YamlResourceNameCollectionFactory implements ResourceNameCollectionFactoryInterface
 {
-    private $yamlParser;
     private $paths;
     private $decorated;
 
@@ -32,13 +32,14 @@ final class YamlResourceNameCollectionFactory implements ResourceNameCollectionF
      */
     public function __construct(array $paths, ResourceNameCollectionFactoryInterface $decorated = null)
     {
-        $this->yamlParser = new YamlParser();
         $this->paths = $paths;
         $this->decorated = $decorated;
     }
 
     /**
      * {@inheritdoc}
+     *
+     * @throws ParseException
      */
     public function create() : ResourceNameCollection
     {
@@ -50,7 +51,14 @@ public function create() : ResourceNameCollection
         }
 
         foreach ($this->paths as $path) {
-            $resources = $this->yamlParser->parse(file_get_contents($path));
+            try {
+                $resources = Yaml::parse(file_get_contents($path));
+            } catch (ParseException $parseException) {
+                $parseException->setParsedFile($path);
+
+                throw $parseException;
+            }
+
             $resources = $resources['resources'] ?? $resources;
 
             foreach ($resources as $resource) {
diff --git a/src/Metadata/schema/metadata.xsd b/src/Metadata/schema/metadata.xsd
index 8564d2adc38..0b6b1271a88 100644
--- a/src/Metadata/schema/metadata.xsd
+++ b/src/Metadata/schema/metadata.xsd
@@ -17,6 +17,7 @@
                 <xsd:element name="itemOperation" minOccurs="0" maxOccurs="unbounded" type="attribute"/>
                 <xsd:element name="collectionOperation" minOccurs="0" maxOccurs="unbounded" type="attribute"/>
                 <xsd:element name="attribute" minOccurs="0" maxOccurs="unbounded" type="attribute"/>
+                <xsd:element name="property" minOccurs="0" maxOccurs="unbounded" type="property"/>
             </xsd:sequence>
             <xsd:attribute type="xsd:string" name="class" use="required"/>
             <xsd:attribute type="xsd:string" name="shortName"/>
@@ -30,4 +31,19 @@
         </xsd:choice>
         <xsd:attribute type="xsd:string" name="name"/>
     </xsd:complexType>
+
+    <xsd:complexType name="property">
+        <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="attribute" minOccurs="0" maxOccurs="unbounded" type="attribute"/>
+        </xsd:sequence>
+        <xsd:attribute type="xsd:string" name="name"/>
+        <xsd:attribute type="xsd:string" name="description"/>
+        <xsd:attribute type="xsd:string" name="iri"/>
+        <xsd:attribute type="xsd:boolean" name="readable"/>
+        <xsd:attribute type="xsd:boolean" name="writable"/>
+        <xsd:attribute type="xsd:boolean" name="readableLink"/>
+        <xsd:attribute type="xsd:boolean" name="writableLink"/>
+        <xsd:attribute type="xsd:boolean" name="required"/>
+        <xsd:attribute type="xsd:boolean" name="identifier"/>
+    </xsd:complexType>
 </xsd:schema>
diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
index 9e8de1ab717..267c90eb03f 100644
--- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
+++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
@@ -219,6 +219,16 @@ private function getContainerBuilderProphecy()
         $definition = $definitionProphecy->reveal();
         $containerBuilderProphecy->getDefinition('api_platform.metadata.resource.metadata_factory.yaml')->willReturn($definition)->shouldBeCalled();
 
+        $definitionProphecy = $this->prophesize(Definition::class);
+        $definitionProphecy->replaceArgument(0, [])->shouldBeCalled();
+        $definition = $definitionProphecy->reveal();
+        $containerBuilderProphecy->getDefinition('api_platform.metadata.property.name_collection_factory.yaml')->willReturn($definition)->shouldBeCalled();
+
+        $definitionProphecy = $this->prophesize(Definition::class);
+        $definitionProphecy->replaceArgument(0, [])->shouldBeCalled();
+        $definition = $definitionProphecy->reveal();
+        $containerBuilderProphecy->getDefinition('api_platform.metadata.property.metadata_factory.yaml')->willReturn($definition)->shouldBeCalled();
+
         $definitionProphecy = $this->prophesize(Definition::class);
         $definitionProphecy->replaceArgument(0, [])->shouldBeCalled();
         $definition = $definitionProphecy->reveal();
@@ -229,6 +239,16 @@ private function getContainerBuilderProphecy()
         $definition = $definitionProphecy->reveal();
         $containerBuilderProphecy->getDefinition('api_platform.metadata.resource.metadata_factory.xml')->willReturn($definition)->shouldBeCalled();
 
+        $definitionProphecy = $this->prophesize(Definition::class);
+        $definitionProphecy->replaceArgument(0, [])->shouldBeCalled();
+        $definition = $definitionProphecy->reveal();
+        $containerBuilderProphecy->getDefinition('api_platform.metadata.property.name_collection_factory.xml')->willReturn($definition)->shouldBeCalled();
+
+        $definitionProphecy = $this->prophesize(Definition::class);
+        $definitionProphecy->replaceArgument(0, [])->shouldBeCalled();
+        $definition = $definitionProphecy->reveal();
+        $containerBuilderProphecy->getDefinition('api_platform.metadata.property.metadata_factory.xml')->willReturn($definition)->shouldBeCalled();
+
         $definitions = [
             'api_platform.action.documentation',
             'api_platform.action.placeholder',
@@ -301,9 +321,13 @@ private function getContainerBuilderProphecy()
             'api_platform.metadata.property.metadata_factory.property_info',
             'api_platform.metadata.property.metadata_factory.serializer',
             'api_platform.metadata.property.metadata_factory.validator',
+            'api_platform.metadata.property.metadata_factory.xml',
+            'api_platform.metadata.property.metadata_factory.yaml',
             'api_platform.metadata.property.name_collection_factory.cached',
             'api_platform.metadata.property.name_collection_factory.inherited',
             'api_platform.metadata.property.name_collection_factory.property_info',
+            'api_platform.metadata.property.name_collection_factory.xml',
+            'api_platform.metadata.property.name_collection_factory.yaml',
             'api_platform.metadata.resource.metadata_factory.annotation',
             'api_platform.metadata.resource.metadata_factory.cached',
             'api_platform.metadata.resource.metadata_factory.operation',
diff --git a/tests/Fixtures/FileConfigurations/parse_exception.yml b/tests/Fixtures/FileConfigurations/parse_exception.yml
new file mode 100644
index 00000000000..87ff9d1e3bb
--- /dev/null
+++ b/tests/Fixtures/FileConfigurations/parse_exception.yml
@@ -0,0 +1,2 @@
+parse
+  exception
diff --git a/tests/Fixtures/FileConfigurations/propertiesinvalid.yml b/tests/Fixtures/FileConfigurations/propertiesinvalid.yml
new file mode 100644
index 00000000000..eac93bc1004
--- /dev/null
+++ b/tests/Fixtures/FileConfigurations/propertiesinvalid.yml
@@ -0,0 +1,4 @@
+resources:
+    configdummy:
+        class:  'ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy'
+        properties: 'invalid'
diff --git a/tests/Fixtures/FileConfigurations/propertyinvalid.xml b/tests/Fixtures/FileConfigurations/propertyinvalid.xml
new file mode 100644
index 00000000000..5d5cd30bfa1
--- /dev/null
+++ b/tests/Fixtures/FileConfigurations/propertyinvalid.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" ?>
+
+<resources>
+    <resource class="ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy">
+        <property name="foo">
+            <foo>Foo</foo>
+        </property>
+    </resource>
+</resources>
diff --git a/tests/Fixtures/FileConfigurations/propertyinvalid.yml b/tests/Fixtures/FileConfigurations/propertyinvalid.yml
new file mode 100644
index 00000000000..fe07282f62a
--- /dev/null
+++ b/tests/Fixtures/FileConfigurations/propertyinvalid.yml
@@ -0,0 +1,5 @@
+resources:
+    configdummy:
+        class:  'ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy'
+        properties:
+            'foo': 'invalid'
diff --git a/tests/Fixtures/FileConfigurations/resources.xml b/tests/Fixtures/FileConfigurations/resources.xml
index 9dec432e086..f7cc41049c3 100644
--- a/tests/Fixtures/FileConfigurations/resources.xml
+++ b/tests/Fixtures/FileConfigurations/resources.xml
@@ -34,5 +34,28 @@
             <attribute name="@type">hydra:Operation</attribute>
             <attribute name="@hydra:title">File config Dummy</attribute>
         </attribute>
+
+        <property
+                name="foo"
+                description="The dummy foo"
+                readable="true"
+                writable="true"
+                readableLink="false"
+                writableLink="false"
+                required="true"
+        >
+            <attribute name="foo">
+                <attribute>Foo</attribute>
+            </attribute>
+            <attribute name="bar">
+                <attribute>
+                    <attribute>Bar</attribute>
+                </attribute>
+                <attribute name="baz">Baz</attribute>
+            </attribute>
+            <attribute name="baz">Baz</attribute>
+        </property>
+
+        <property name="name" description="The dummy name" />
     </resource>
 </resources>
diff --git a/tests/Fixtures/FileConfigurations/resources.yml b/tests/Fixtures/FileConfigurations/resources.yml
index 4ffaa1e9a42..3b574d15a17 100644
--- a/tests/Fixtures/FileConfigurations/resources.yml
+++ b/tests/Fixtures/FileConfigurations/resources.yml
@@ -23,3 +23,19 @@ resources:
                 '@type': 'hydra:Operation'
                 '@hydra:title': 'File config Dummy'
         iri: 'someirischema'
+        properties:
+            'foo':
+                description: 'The dummy foo'
+                readable: true
+                writable: true
+                readableLink: false
+                writableLink: false
+                required: true
+                attributes:
+                    'foo': ['Foo']
+                    'bar':
+                        0: ['Bar']
+                        'baz': 'Baz'
+                    'baz': 'Baz'
+            'name':
+                description: 'The dummy name'
diff --git a/tests/Fixtures/FileConfigurations/resourcesinvalid.yml b/tests/Fixtures/FileConfigurations/resourcesinvalid.yml
new file mode 100644
index 00000000000..16b01989f54
--- /dev/null
+++ b/tests/Fixtures/FileConfigurations/resourcesinvalid.yml
@@ -0,0 +1 @@
+resources: 'invalid'
diff --git a/tests/Fixtures/TestBundle/Entity/FileConfigDummy.php b/tests/Fixtures/TestBundle/Entity/FileConfigDummy.php
index db32369f7f0..7b6b15c958f 100644
--- a/tests/Fixtures/TestBundle/Entity/FileConfigDummy.php
+++ b/tests/Fixtures/TestBundle/Entity/FileConfigDummy.php
@@ -36,6 +36,13 @@ class FileConfigDummy
      */
     private $name;
 
+    /**
+     * @var string
+     *
+     * @ORM\Column
+     */
+    private $foo;
+
     public function getId()
     {
         return $this->id;
@@ -50,4 +57,14 @@ public function getName()
     {
         return $this->name;
     }
+
+    public function setFoo($foo)
+    {
+        $this->foo = $foo;
+    }
+
+    public function getFoo()
+    {
+        return $this->foo;
+    }
 }
diff --git a/tests/Fixtures/TestBundle/Resources/config/api_resources.yml b/tests/Fixtures/TestBundle/Resources/config/api_resources.yml
index 0d9c6aaf858..cfa777ec990 100644
--- a/tests/Fixtures/TestBundle/Resources/config/api_resources.yml
+++ b/tests/Fixtures/TestBundle/Resources/config/api_resources.yml
@@ -7,3 +7,6 @@ resources:
             custom_operation:  
                 method: 'GET'
                 controller: 'app.config_dummy_resource.action'
+        properties:
+            foo:
+                description: 'The dummy foo'
diff --git a/tests/Metadata/Property/Factory/FileConfigurationMetadataFactoryProvider.php b/tests/Metadata/Property/Factory/FileConfigurationMetadataFactoryProvider.php
new file mode 100644
index 00000000000..b4bac39f1e9
--- /dev/null
+++ b/tests/Metadata/Property/Factory/FileConfigurationMetadataFactoryProvider.php
@@ -0,0 +1,75 @@
+<?php
+
+/*
+ * This file is part of the API Platform project.
+ *
+ * (c) Kévin Dunglas <dunglas@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Tests\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
+
+/**
+ * Property metadata provider for file configured factories tests.
+ *
+ * @author Baptiste Meyer <baptiste.meyer@gmail.com>
+ */
+abstract class FileConfigurationMetadataFactoryProvider extends \PHPUnit_Framework_TestCase
+{
+    public function propertyMetadataProvider()
+    {
+        $metadata = [
+            'description' => 'The dummy foo',
+            'readable' => true,
+            'writable' => true,
+            'readableLink' => false,
+            'writableLink' => false,
+            'required' => true,
+            'attributes' => [
+                'foo' => [
+                    'Foo',
+                ],
+                'bar' => [
+                    ['Bar'],
+                    'baz' => 'Baz',
+                ],
+                'baz' => 'Baz',
+            ],
+        ];
+
+        return [[$this->getPropertyMetadata($metadata)]];
+    }
+
+    public function decoratedPropertyMetadataProvider()
+    {
+        $metadata = [
+            'description' => 'The dummy foo',
+            'readable' => true,
+            'writable' => true,
+            'readableLink' => true,
+            'writableLink' => false,
+            'required' => true,
+            'identifier' => false,
+            'attributes' => [
+                'Foo',
+            ],
+        ];
+
+        return [[$this->getPropertyMetadata($metadata)]];
+    }
+
+    private function getPropertyMetadata(array $metadata) : PropertyMetadata
+    {
+        $propertyMetadata = new PropertyMetadata();
+
+        foreach ($metadata as $propertyName => $propertyValue) {
+            $propertyMetadata = $propertyMetadata->{'with'.ucfirst($propertyName)}($propertyValue);
+        }
+
+        return $propertyMetadata;
+    }
+}
diff --git a/tests/Metadata/Property/Factory/XmlPropertyMetadataFactoryTest.php b/tests/Metadata/Property/Factory/XmlPropertyMetadataFactoryTest.php
new file mode 100644
index 00000000000..747ee53fd70
--- /dev/null
+++ b/tests/Metadata/Property/Factory/XmlPropertyMetadataFactoryTest.php
@@ -0,0 +1,90 @@
+<?php
+
+/*
+ * This file is part of the API Platform project.
+ *
+ * (c) Kévin Dunglas <dunglas@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Tests\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
+use ApiPlatform\Core\Metadata\Property\Factory\XmlPropertyMetadataFactory;
+use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy;
+
+/**
+ * @author Baptiste Meyer <baptiste.meyer@gmail.com>
+ */
+class XmlPropertyMetadataFactoryTest extends FileConfigurationMetadataFactoryProvider
+{
+    /**
+     * @dataProvider propertyMetadataProvider
+     */
+    public function testCreate(PropertyMetadata $expectedPropertyMetadata)
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.xml';
+
+        $propertyMetadataFactory = new XmlPropertyMetadataFactory([$configPath]);
+        $propertyMetadata = $propertyMetadataFactory->create(FileConfigDummy::class, 'foo');
+
+        $this->assertInstanceOf(PropertyMetadata::class, $propertyMetadata);
+        $this->assertEquals($expectedPropertyMetadata, $propertyMetadata);
+    }
+
+    /**
+     * @dataProvider decoratedPropertyMetadataProvider
+     */
+    public function testCreateWithParentPropertyMetadataFactory(PropertyMetadata $expectedPropertyMetadata)
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.xml';
+
+        $decorated = $this->prophesize(PropertyMetadataFactoryInterface::class);
+        $decorated
+            ->create(FileConfigDummy::class, 'foo', [])
+            ->willReturn(new PropertyMetadata(null, null, null, null, true, null, null, false, null, null, ['Foo']))
+            ->shouldBeCalled();
+
+        $propertyMetadataFactory = new XmlPropertyMetadataFactory([$configPath], $decorated->reveal());
+        $propertyMetadata = $propertyMetadataFactory->create(FileConfigDummy::class, 'foo');
+
+        $this->assertInstanceOf(PropertyMetadata::class, $propertyMetadata);
+        $this->assertEquals($expectedPropertyMetadata, $propertyMetadata);
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\PropertyNotFoundException
+     * @expectedExceptionMessage Property "foo" of the resource class "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist" not found.
+     */
+    public function testCreateWithNonexistentResource()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcenotfound.xml';
+
+        (new XmlPropertyMetadataFactory([$configPath]))->create(\ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist::class, 'foo');
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\PropertyNotFoundException
+     * @expectedExceptionMessage Property "bar" of the resource class "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy" not found.
+     */
+    public function testCreateWithNonexistentProperty()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.xml';
+
+        (new XmlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'bar');
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+     * @expectedExceptionMessageRegExp /.+Element 'foo': This element is not expected\..+/
+     */
+    public function testCreateWithInvalidXml()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/propertyinvalid.xml';
+
+        (new XmlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'foo');
+    }
+}
diff --git a/tests/Metadata/Property/Factory/XmlPropertyNameCollectionFactoryTest.php b/tests/Metadata/Property/Factory/XmlPropertyNameCollectionFactoryTest.php
new file mode 100644
index 00000000000..38a8c16b680
--- /dev/null
+++ b/tests/Metadata/Property/Factory/XmlPropertyNameCollectionFactoryTest.php
@@ -0,0 +1,71 @@
+<?php
+
+/*
+ * This file is part of the API Platform project.
+ *
+ * (c) Kévin Dunglas <dunglas@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Tests\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
+use ApiPlatform\Core\Metadata\Property\Factory\XmlPropertyNameCollectionFactory;
+use ApiPlatform\Core\Metadata\Property\PropertyNameCollection;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy;
+
+/**
+ * @author Baptiste Meyer <baptiste.meyer@gmail.com>
+ */
+class XmlPropertyNameCollectionFactoryTest extends \PHPUnit_Framework_TestCase
+{
+    public function testCreate()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.xml';
+
+        $this->assertEquals(
+            (new XmlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class),
+            new PropertyNameCollection(['foo', 'name'])
+        );
+    }
+
+    public function testCreateWithParentPropertyNameCollectionFactory()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.xml';
+
+        $decorated = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
+        $decorated
+            ->create(FileConfigDummy::class, [])
+            ->willReturn(new PropertyNameCollection(['id']))
+            ->shouldBeCalled();
+
+        $this->assertEquals(
+            (new XmlPropertyNameCollectionFactory([$configPath], $decorated->reveal()))->create(FileConfigDummy::class),
+            new PropertyNameCollection(['foo', 'name', 'id'])
+        );
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\ResourceClassNotFoundException
+     * @expectedExceptionMessage The resource class "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist" does not exist.
+     */
+    public function testCreateWithNonexistentResource()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcenotfound.xml';
+
+        (new XmlPropertyNameCollectionFactory([$configPath]))->create(\ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist::class);
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+     * @expectedExceptionMessageRegExp /.+Element 'foo': This element is not expected\..+/
+     */
+    public function testCreateWithInvalidXml()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/propertyinvalid.xml';
+
+        (new XmlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class);
+    }
+}
diff --git a/tests/Metadata/Property/Factory/YamlPropertyMetadataFactoryTest.php b/tests/Metadata/Property/Factory/YamlPropertyMetadataFactoryTest.php
new file mode 100644
index 00000000000..8c902002d4f
--- /dev/null
+++ b/tests/Metadata/Property/Factory/YamlPropertyMetadataFactoryTest.php
@@ -0,0 +1,133 @@
+<?php
+
+/*
+ * This file is part of the API Platform project.
+ *
+ * (c) Kévin Dunglas <dunglas@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Tests\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
+use ApiPlatform\Core\Metadata\Property\Factory\YamlPropertyMetadataFactory;
+use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy;
+
+/**
+ * @author Baptiste Meyer <baptiste.meyer@gmail.com>
+ */
+class YamlPropertyMetadataFactoryTest extends FileConfigurationMetadataFactoryProvider
+{
+    /**
+     * @dataProvider propertyMetadataProvider
+     */
+    public function testCreate(PropertyMetadata $expectedPropertyMetadata)
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.yml';
+
+        $propertyMetadataFactory = new YamlPropertyMetadataFactory([$configPath]);
+        $propertyMetadata = $propertyMetadataFactory->create(FileConfigDummy::class, 'foo');
+
+        $this->assertInstanceOf(PropertyMetadata::class, $propertyMetadata);
+        $this->assertEquals($expectedPropertyMetadata, $propertyMetadata);
+    }
+
+    /**
+     * @dataProvider decoratedPropertyMetadataProvider
+     */
+    public function testCreateWithParentPropertyMetadataFactory(PropertyMetadata $expectedPropertyMetadata)
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.yml';
+
+        $decorated = $this->prophesize(PropertyMetadataFactoryInterface::class);
+        $decorated
+            ->create(FileConfigDummy::class, 'foo', [])
+            ->willReturn(new PropertyMetadata(null, null, null, null, true, null, null, false, null, null, ['Foo']))
+            ->shouldBeCalled();
+
+        $propertyMetadataFactory = new YamlPropertyMetadataFactory([$configPath], $decorated->reveal());
+        $propertyMetadata = $propertyMetadataFactory->create(FileConfigDummy::class, 'foo');
+
+        $this->assertInstanceOf(PropertyMetadata::class, $propertyMetadata);
+        $this->assertEquals($expectedPropertyMetadata, $propertyMetadata);
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\PropertyNotFoundException
+     * @expectedExceptionMessage Property "foo" of the resource class "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist" not found.
+     */
+    public function testCreateWithNonexistentResource()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcenotfound.yml';
+
+        (new YamlPropertyMetadataFactory([$configPath]))->create(\ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist::class, 'foo');
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\PropertyNotFoundException
+     * @expectedExceptionMessage Property "bar" of the resource class "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy" not found.
+     */
+    public function testCreateWithNonexistentProperty()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.yml';
+
+        (new YamlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'bar');
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+     * @expectedExceptionMessageRegExp /"resources" setting is expected to be null or an array, string given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/resourcesinvalid\.yml"\./
+     */
+    public function testCreateWithMalformedResourcesSetting()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcesinvalid.yml';
+
+        (new YamlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'foo');
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+     * @expectedExceptionMessageRegExp /"properties" setting is expected to be null or an array, string given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/propertiesinvalid\.yml"\./
+     */
+    public function testCreateWithMalformedPropertiesSetting()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/propertiesinvalid.yml';
+
+        (new YamlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'foo');
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+     * @expectedExceptionMessageRegExp /"foo" setting is expected to be null or an array, string given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/propertyinvalid\.yml"\./
+     */
+    public function testCreateWithMalformedPropertySetting()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/propertyinvalid.yml';
+
+        (new YamlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'foo');
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+     * @expectedExceptionMessageRegExp /"class" setting is expected to be a string, none given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/resourcenoclass\.yml"\./
+     */
+    public function testCreateWithoutResourceClass()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcenoclass.yml';
+
+        (new YamlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'foo');
+    }
+
+    /**
+     * @expectedException \Symfony\Component\Yaml\Exception\ParseException
+     */
+    public function testCreateWithMalformedYaml()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/parse_exception.yml';
+
+        (new YamlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'foo');
+    }
+}
diff --git a/tests/Metadata/Property/Factory/YamlPropertyNameCollectionFactoryTest.php b/tests/Metadata/Property/Factory/YamlPropertyNameCollectionFactoryTest.php
new file mode 100644
index 00000000000..83d8219a327
--- /dev/null
+++ b/tests/Metadata/Property/Factory/YamlPropertyNameCollectionFactoryTest.php
@@ -0,0 +1,114 @@
+<?php
+
+/*
+ * This file is part of the API Platform project.
+ *
+ * (c) Kévin Dunglas <dunglas@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Tests\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
+use ApiPlatform\Core\Metadata\Property\Factory\YamlPropertyNameCollectionFactory;
+use ApiPlatform\Core\Metadata\Property\PropertyNameCollection;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy;
+
+/**
+ * @author Baptiste Meyer <baptiste.meyer@gmail.com>
+ */
+class YamlPropertyNameCollectionFactoryTest extends \PHPUnit_Framework_TestCase
+{
+    public function testCreate()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.yml';
+
+        $this->assertEquals(
+            (new YamlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class),
+            new PropertyNameCollection(['foo', 'name'])
+        );
+    }
+
+    public function testCreateWithParentPropertyMetadataFactory()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.yml';
+
+        $decorated = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
+        $decorated
+            ->create(FileConfigDummy::class, [])
+            ->willReturn(new PropertyNameCollection(['id']))
+            ->shouldBeCalled();
+
+        $this->assertEquals(
+            (new YamlPropertyNameCollectionFactory([$configPath], $decorated->reveal()))->create(FileConfigDummy::class),
+            new PropertyNameCollection(['foo', 'name', 'id'])
+        );
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\ResourceClassNotFoundException
+     * @expectedExceptionMessage The resource class "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist" does not exist.
+     */
+    public function testCreateWithNonexistentResource()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcenotfound.yml';
+
+        (new YamlPropertyNameCollectionFactory([$configPath]))->create(\ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist::class);
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+     * @expectedExceptionMessageRegExp /"resources" setting is expected to be null or an array, string given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/resourcesinvalid\.yml"\./
+     */
+    public function testCreateWithMalformedResourcesSetting()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcesinvalid.yml';
+
+        (new YamlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class);
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+     * @expectedExceptionMessageRegExp /"properties" setting is expected to be null or an array, string given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/propertiesinvalid\.yml"\./
+     */
+    public function testCreateWithMalformedPropertiesSetting()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/propertiesinvalid.yml';
+
+        (new YamlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class);
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+     * @expectedExceptionMessageRegExp /"foo" setting is expected to be null or an array, string given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/propertyinvalid\.yml"\./
+     */
+    public function testCreateWithMalformedPropertySetting()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/propertyinvalid.yml';
+
+        (new YamlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class);
+    }
+
+    /**
+     * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+     * @expectedExceptionMessageRegExp /"class" setting is expected to be a string, none given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/resourcenoclass\.yml"\./
+     */
+    public function testCreateWithoutResourceClass()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcenoclass.yml';
+
+        (new YamlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class);
+    }
+
+    /**
+     * @expectedException \Symfony\Component\Yaml\Exception\ParseException
+     */
+    public function testCreateWithMalformedYaml()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/parse_exception.yml';
+
+        (new YamlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class);
+    }
+}
diff --git a/tests/Metadata/Resource/Factory/FileConfigurationMetadataFactoryProvider.php b/tests/Metadata/Resource/Factory/FileConfigurationMetadataFactoryProvider.php
index b70bcf78184..86f88ce8260 100644
--- a/tests/Metadata/Resource/Factory/FileConfigurationMetadataFactoryProvider.php
+++ b/tests/Metadata/Resource/Factory/FileConfigurationMetadataFactoryProvider.php
@@ -14,7 +14,7 @@
 use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
 
 /**
- * Resource metadata provider for file configurated factories tests.
+ * Resource metadata provider for file configured factories tests.
  *
  * @author Antoine Bluchet <soyuka@gmail.com>
  */
diff --git a/tests/Metadata/Resource/Factory/YamlResourceMetadataFactoryTest.php b/tests/Metadata/Resource/Factory/YamlResourceMetadataFactoryTest.php
index d2a8db7b42e..cb60ae70189 100644
--- a/tests/Metadata/Resource/Factory/YamlResourceMetadataFactoryTest.php
+++ b/tests/Metadata/Resource/Factory/YamlResourceMetadataFactoryTest.php
@@ -127,4 +127,14 @@ public function testYamlExistingParentResourceMetadataFactory(ResourceMetadata $
 
         $this->assertEquals($expectedResourceMetadata, $resourceMetadata);
     }
+
+    /**
+     * @expectedException \Symfony\Component\Yaml\Exception\ParseException
+     */
+    public function testCreateWithMalformedYaml()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/parse_exception.yml';
+
+        (new YamlResourceMetadataFactory([$configPath]))->create(FileConfigDummy::class);
+    }
 }
diff --git a/tests/Metadata/Resource/Factory/YamlResourceNameCollectionFactoryTest.php b/tests/Metadata/Resource/Factory/YamlResourceNameCollectionFactoryTest.php
index dc629c3dade..2288f1c43fd 100644
--- a/tests/Metadata/Resource/Factory/YamlResourceNameCollectionFactoryTest.php
+++ b/tests/Metadata/Resource/Factory/YamlResourceNameCollectionFactoryTest.php
@@ -55,4 +55,14 @@ public function testNoClassYamlResourceNameCollectionFactory()
 
         $resourceMetadataFactory->create();
     }
+
+    /**
+     * @expectedException \Symfony\Component\Yaml\Exception\ParseException
+     */
+    public function testCreateWithMalformedYaml()
+    {
+        $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/parse_exception.yml';
+
+        (new YamlResourceNameCollectionFactory([$configPath]))->create();
+    }
 }