You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by dk...@apache.org on 2019/11/08 15:32:44 UTC

[sling-org-apache-sling-app-cms] branch master updated: Fixed SLING-8836 and SLING-8835: Adding a grid view for the sites section and the ability to create named transformations of images

This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git


The following commit(s) were added to refs/heads/master by this push:
     new 38833c0  Fixed SLING-8836 and SLING-8835: Adding a grid view for the sites section and the ability to create named transformations of images
38833c0 is described below

commit 38833c0315a62ecce65e1ad61a6c2be14cceb8fe
Author: Dan Klco <dk...@apache.org>
AuthorDate: Wed Nov 6 20:20:30 2019 -0600

    Fixed SLING-8836 and SLING-8835: Adding a grid view for the sites section and the ability to create named transformations of images
---
 builder/src/main/provisioning/cms.txt              |  10 ++
 .../core/internal/operations/MembersOperation.java |   8 +-
 .../internal/operations/UpdateStatusOperation.java |   5 +-
 transformer/pom.xml                                |  13 ++-
 .../cms/transformer/FileThumbnailTransformer.java  |  61 -----------
 .../sling/cms/transformer/ThumbnailProvider.java   |  20 ++--
 ...eThumbnailProvider.java => Transformation.java} |  26 ++---
 .../cms/transformer/TransformationHandler.java     |  28 ++---
 ...ailProvider.java => TransformationManager.java} |  33 +++---
 .../{ThumbnailProvider.java => Transformer.java}   |  33 +++---
 .../cms/transformer/internal/CropHandler.java      |  17 +--
 .../internal/ImageThumbnailProvider.java           |  17 ++-
 .../transformer/internal/PdfThumbnailProvider.java |  17 ++-
 .../cms/transformer/internal/RenditionCleaner.java |  56 ++++++++++
 .../cms/transformer/internal/SizeHandler.java      |  31 +++---
 .../internal/SlideShowThumbnailProvider.java       |  22 ++--
 .../transformer/internal/TikaFallbackProvider.java |  20 ++--
 .../cms/transformer/internal/TransformServlet.java |  99 ++++++++++++++++--
 .../internal/TransformationServiceUser.java        |  48 +++++++++
 ...ilTransformerImpl.java => TransformerImpl.java} |  93 +++++++----------
 .../internal/TransformerWebConsole.java            |  11 +-
 .../internal/models/TransformationImpl.java        |  54 ++++++++++
 .../internal/models/TransformationManagerImpl.java |  49 +++++++++
 .../apache/sling/cms/transformer/package-info.java |  17 +--
 .../cms/transformer/internal/CropHandlerTest.java  |  35 ++++---
 .../internal/FileThumbnailTransformerImplTest.java |  77 --------------
 .../internal/ImageThumbnailProviderTest.java       |  13 +--
 .../internal/PdfThumbnailProviderTest.java         |  13 +--
 .../cms/transformer/internal/SizeHandlerTest.java  |  44 +++++---
 .../internal/SlideShowThumbnailProviderTest.java   |  20 ++--
 .../internal/TikaFallbackProviderTest.java         |   8 +-
 .../transformer/internal/TransformServletTest.java |  88 ++++++++++++----
 .../transformer/internal/TransformerImplTest.java  |  94 +++++++++++++++++
 transformer/src/test/resources/content.json        |  24 +++--
 ui/src/main/frontend/js/cms.nav.js                 |  68 ++++++++++++
 ui/src/main/frontend/js/cms.table.js               |  85 ---------------
 ui/src/main/frontend/scss/cms.scss                 |  55 ++++++++--
 .../resources/SLING-INF/nodetypes/nodetypes.cnd    |   1 +
 ui/src/main/resources/jcr_root/conf/global.json    |  65 +++++++++++-
 .../sling-cms/components/caconfig/edit/edit.jsp    |   4 +-
 .../components/caconfig/template/config/config.jsp |   3 +
 .../components/caconfig/template/config/edit.json  |  13 +++
 .../components/caconfig/template/template.jsp      |  12 ++-
 .../components/caconfig/transformation/config.json |   4 +
 .../config/config.jsp}                             |  22 +++-
 .../caconfig/transformation/config/edit.json       |  16 +++
 .../transformation.jsp}                            |  12 ++-
 .../caconfig/transformationhandlers/crop.json      |   5 +
 .../crop/crop.jsp}                                 |   9 +-
 .../caconfig/transformationhandlers/crop/edit.json |  53 ++++++++++
 .../caconfig/transformationhandlers/size.json      |   5 +
 .../caconfig/transformationhandlers/size/edit.json |  23 +++++
 .../size/size.jsp}                                 |  11 +-
 .../components/caconfig/transformations.json       |   5 +
 .../transformations.jsp}                           |   6 +-
 .../cms/contentactions/contentactions.jsp          |  65 +++++++++---
 .../cms/contentbreadcrumb/contentbreadcrumb.jsp    |  24 ++---
 .../components/cms/contentgrid/contentgrid.jsp     | 114 +++++++++++++++++++++
 .../contentlayout/contentlayout.jsp}               |  15 ++-
 .../components/cms/contenttable/contenttable.jsp   |   4 +-
 .../components/cms/i18ncontainer/i18ncontainer.jsp |  89 ++++++++++++++++
 .../components/cms/i18ntable/i18ntable.jsp         |  87 ----------------
 .../fields/thumbnail/thumbnail.jsp}                |  10 +-
 .../libs/sling-cms/components/pages/base/nav.jsp   |   5 +-
 .../libs/sling-cms/content/auth/user/profile.json  |  39 +++++++
 .../libs/sling-cms/content/i18n/dictionary.json    |  92 ++++++++---------
 .../libs/sling-cms/content/site/content.json       |   3 +-
 .../libs/sling-cms/content/site/editgroup.json     |   6 ++
 .../libs/sling-cms/content/site/sites.json         |   3 +-
 .../jcr_root/libs/sling-cms/content/start.json     |   3 +
 .../sling-cms/content/transformations/create.json  |  55 ++++++++++
 .../sling-cms/content/transformations/edit.json    |  27 +++++
 .../sling-cms/content/transformations/editor.json  | 102 ++++++++++++++++++
 73 files changed, 1689 insertions(+), 735 deletions(-)

diff --git a/builder/src/main/provisioning/cms.txt b/builder/src/main/provisioning/cms.txt
index 2beb5a0..6390bd9 100644
--- a/builder/src/main/provisioning/cms.txt
+++ b/builder/src/main/provisioning/cms.txt
@@ -100,6 +100,11 @@
         allow   jcr:write,jcr:nodeTypeManagement,jcr:versionManagement    on /static
         allow   jcr:read    on /
     end
+    create service user sling-cms-transformer
+    set ACL for sling-cms-metadata
+        allow   jcr:write,jcr:nodeTypeManagement,jcr:versionManagement    on /content
+        allow   jcr:read    on /conf
+    end
     create service user sling-rewriter
     set ACL for sling-rewriter
         allow   jcr:read    on /
@@ -125,6 +130,11 @@
         "org.apache.sling.cms.core:sling-cms-metadata\=sling-cms-metadata"
     ]
 
+  org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-sling-cms-transformer
+     user.mapping=[
+        "org.apache.sling.cms.transformer:sling-cms-transformer\=sling-cms-transformer"
+    ]
+
   org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-sling-cms-versionmgr
      user.mapping=[
         "org.apache.sling.cms.core:sling-cms-versionmgr\=sling-cms-versionmgr"
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/operations/MembersOperation.java b/core/src/main/java/org/apache/sling/cms/core/internal/operations/MembersOperation.java
index 4a9f855..761e132 100644
--- a/core/src/main/java/org/apache/sling/cms/core/internal/operations/MembersOperation.java
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/operations/MembersOperation.java
@@ -54,7 +54,9 @@ public class MembersOperation implements PostOperation {
             List<String> auths = new ArrayList<>();
             Optional.ofNullable(request.getParameterValues(PN_MEMBERS)).ifPresent(p -> auths.addAll(Arrays.asList(p)));
 
-            AuthorizableWrapper groupWrapper = request.getResource().adaptTo(AuthorizableWrapper.class);
+            AuthorizableWrapper groupWrapper = Optional
+                    .ofNullable(request.getResource().adaptTo(AuthorizableWrapper.class))
+                    .orElseThrow(() -> new RepositoryException("Failed to get group"));
             if (!groupWrapper.getAuthorizable().isGroup()) {
                 throw new RepositoryException("Provided authorizable is not a group");
             }
@@ -81,7 +83,9 @@ public class MembersOperation implements PostOperation {
                 if (resource == null) {
                     throw new RepositoryException("Failed to resolve authorizable at " + path);
                 }
-                Authorizable authorizable = resource.adaptTo(AuthorizableWrapper.class).getAuthorizable();
+                Authorizable authorizable = Optional.ofNullable(resource.adaptTo(AuthorizableWrapper.class))
+                        .map(AuthorizableWrapper::getAuthorizable)
+                        .orElseThrow(() -> new RepositoryException("Failed to get authorizable from: " + resource));
                 group.addMember(authorizable);
                 changes.add(Modification.onModified(authorizable.getPath()));
                 log.debug("Adding member {} to {}", authorizable, group);
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/operations/UpdateStatusOperation.java b/core/src/main/java/org/apache/sling/cms/core/internal/operations/UpdateStatusOperation.java
index 8f91cf9..6068ae0 100644
--- a/core/src/main/java/org/apache/sling/cms/core/internal/operations/UpdateStatusOperation.java
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/operations/UpdateStatusOperation.java
@@ -18,6 +18,7 @@ package org.apache.sling.cms.core.internal.operations;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 
 import javax.jcr.RepositoryException;
 
@@ -51,7 +52,9 @@ public class UpdateStatusOperation implements PostOperation {
 
             String reason = request.getParameter(PN_REASON);
 
-            AuthorizableWrapper authWrapper = request.getResource().adaptTo(AuthorizableWrapper.class);
+            AuthorizableWrapper authWrapper = Optional
+                    .ofNullable(request.getResource().adaptTo(AuthorizableWrapper.class))
+                    .orElseThrow(() -> new RepositoryException("Failed to get authorizable: " + request.getResource()));
 
             if (authWrapper.getAuthorizable().isGroup()) {
                 throw new RepositoryException("Authorizable is not a user");
diff --git a/transformer/pom.xml b/transformer/pom.xml
index 0a64ec3..1a95c7f 100644
--- a/transformer/pom.xml
+++ b/transformer/pom.xml
@@ -30,6 +30,10 @@
                     <instructions>
                         <Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
                         <Export-Package>org.apache.sling.cms.transformer,net.coobird.thumbnailator.*</Export-Package>
+                        
+                        <Sling-Model-Packages>
+                            org.apache.sling.cms.transformer
+                        </Sling-Model-Packages>
                     </instructions>
                 </configuration>
             </plugin>
@@ -111,10 +115,18 @@
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
+            <artifactId>osgi.annotation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
             <artifactId>org.osgi.service.component.annotations</artifactId>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.metatype.annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
             <artifactId>osgi.core</artifactId>
         </dependency>
         <dependency>
@@ -134,7 +146,6 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.caconfig.api</artifactId>
-            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/FileThumbnailTransformer.java b/transformer/src/main/java/org/apache/sling/cms/transformer/FileThumbnailTransformer.java
deleted file mode 100644
index b54bc2a..0000000
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/FileThumbnailTransformer.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sling.cms.transformer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-import org.apache.sling.api.SlingHttpServletRequest;
-import org.apache.sling.cms.File;
-
-/**
- * Transforms a Sling File into thumbnails using the registered
- * TransformationHandlers to invoke Thumbnails transformations on the file.
- */
-public interface FileThumbnailTransformer {
-
-    /**
-     * Gets the transformation handler for the specified command
-     * 
-     * @param command the command string to use to look up the transformation
-     *                handler.
-     * @return the TransformationHandler from the command string or null if none
-     *         found
-     */
-    TransformationHandler getTransformationHandler(String command);
-
-    /**
-     * Transforms the file into a thumbnail using the specified commands.
-     * 
-     * @param commands the commands to execute
-     * @param format   the format of the file to return
-     * @param out      the Outputstream to write the thumbnail to
-     * @throws IOException an exception occurs transforming the file
-     */
-    void transformFile(File file, String[] commands, OutputFileFormat format, OutputStream out) throws IOException;
-
-    /**
-     * Transforms the file from the resource path into a thumbnail using the
-     * specified commands from the suffix.
-     * 
-     * @param request the request to parse the file and commands from
-     * @param out     the Outputstream to write the thumbnail to
-     * @throws IOException an exception occurs transforming the file
-     */
-    void transformFile(SlingHttpServletRequest request, OutputStream out) throws IOException;
-
-}
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/ThumbnailProvider.java b/transformer/src/main/java/org/apache/sling/cms/transformer/ThumbnailProvider.java
index decfade..93a8b18 100644
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/ThumbnailProvider.java
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/ThumbnailProvider.java
@@ -19,29 +19,29 @@ package org.apache.sling.cms.transformer;
 import java.io.IOException;
 import java.io.InputStream;
 
-import org.apache.sling.cms.File;
+import org.apache.sling.api.resource.Resource;
 
 /**
- * Service for retrieving a thumbnail for the specified File object.
+ * Service for retrieving a thumbnail for the specified Resource.
  */
 public interface ThumbnailProvider {
 
     /**
-     * Returns true if the ThumbnailProvider applies for the specified file.
+     * Returns true if the ThumbnailProvider applies for the specified resource.
      * 
-     * @param file the file to check
-     * @return true if this ThumbnailProvider will create a thumbnail for this file,
-     *         false otherwise
+     * @param resource the resource to check
+     * @return true if this ThumbnailProvider will create a thumbnail for this
+     *         resource, false otherwise
      */
-    boolean applies(File file);
+    boolean applies(Resource resource);
 
     /**
-     * Get the thumbnail from the specified file.
+     * Get the thumbnail from the specified resource.
      * 
-     * @param file the file from which to retrieve the thumbnail
+     * @param resource the resource from which to retrieve the thumbnail
      * @return the thumbnail
      * @throws IOException an exception occurs retrieving the thumbnail
      */
-    InputStream getThumbnail(File file) throws IOException;
+    InputStream getThumbnail(Resource resource) throws IOException;
 
 }
\ No newline at end of file
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/ImageThumbnailProvider.java b/transformer/src/main/java/org/apache/sling/cms/transformer/Transformation.java
similarity index 53%
copy from transformer/src/main/java/org/apache/sling/cms/transformer/internal/ImageThumbnailProvider.java
copy to transformer/src/main/java/org/apache/sling/cms/transformer/Transformation.java
index 516d8fc..2058207 100644
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/ImageThumbnailProvider.java
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/Transformation.java
@@ -14,30 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sling.cms.transformer.internal;
+package org.apache.sling.cms.transformer;
 
-import java.io.InputStream;
+import java.util.List;
 
-import org.apache.sling.cms.File;
-import org.apache.sling.cms.transformer.ThumbnailProvider;
-import org.osgi.service.component.annotations.Component;
-
-import com.google.common.net.MediaType;
+import org.apache.sling.api.resource.Resource;
 
 /**
- * A thumbnail provider for image files.
+ * Model representing a transformation, a series of handlers
  */
-@Component(service = ThumbnailProvider.class)
-public class ImageThumbnailProvider implements ThumbnailProvider {
-
-    @Override
-    public boolean applies(File file) {
-        return MediaType.parse(file.getContentType()).is(MediaType.ANY_IMAGE_TYPE);
-    }
+public interface Transformation {
 
-    @Override
-    public InputStream getThumbnail(File file) {
-        return file.getResource().adaptTo(InputStream.class);
-    }
+    String getName();
 
+    List<Resource> getHandlers();
 }
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/TransformationHandler.java b/transformer/src/main/java/org/apache/sling/cms/transformer/TransformationHandler.java
index 33bb7bd..f8ed7d9 100644
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/TransformationHandler.java
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/TransformationHandler.java
@@ -19,34 +19,34 @@ package org.apache.sling.cms.transformer;
 import java.io.IOException;
 import java.io.InputStream;
 
+import org.apache.sling.api.resource.Resource;
+
 import net.coobird.thumbnailator.Thumbnails.Builder;
 
 /*
- * Transformation handlers handle the transformation of thumbnails using the Thumbnails library.
- * Each transformation handler implements a named transformation command which will be parsed out 
- * of the suffix of the transformation request by splitting the suffix by slashes and checking the 
- * "applies" method of each TransformationHandler, in order. 
+ * Transformation handlers handle the transformation of files using the Thumbnails library.
+ * Each transformation handler implements a transformation command using the specifed configuration.
  */
+@SuppressWarnings("squid:S1214") // I don't like this rule...
 public interface TransformationHandler {
 
+    public static final String HANDLER_RESOURCE_TYPE = "handler.resourceType";
+
     /**
-     * Returns true if the transformation handler should execute for the specified
-     * command.
+     * Get the resource type associated with this handler
      * 
-     * @param command the command to check
-     * @return true if the handler will handle this, false otherwise
+     * @return the handler resource type
      */
-    boolean applies(String command);
+    String getResourceType();
 
     /**
-     * Handles the transformation of the thumbnail using the command values from the
+     * Handles the transformation of the file using the command values from the
      * suffix segment.
      * 
      * @param builder the Thumbnails builder to use / update
-     * @param cmd     the command to parse to retrieve the configuration values for
-     *                the transformation
-     * @throws IOException an exception occurs transforming the thumbnail
+     * @param config  the configuration values for the transformation
+     * @throws IOException an exception occurs transforming the file
      */
-    void handle(Builder<? extends InputStream> builder, String cmd) throws IOException;
+    void handle(Builder<? extends InputStream> builder, Resource config) throws IOException;
 
 }
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/ImageThumbnailProvider.java b/transformer/src/main/java/org/apache/sling/cms/transformer/TransformationManager.java
similarity index 53%
copy from transformer/src/main/java/org/apache/sling/cms/transformer/internal/ImageThumbnailProvider.java
copy to transformer/src/main/java/org/apache/sling/cms/transformer/TransformationManager.java
index 516d8fc..b32457a 100644
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/ImageThumbnailProvider.java
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/TransformationManager.java
@@ -14,30 +14,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sling.cms.transformer.internal;
+package org.apache.sling.cms.transformer;
 
-import java.io.InputStream;
-
-import org.apache.sling.cms.File;
-import org.apache.sling.cms.transformer.ThumbnailProvider;
-import org.osgi.service.component.annotations.Component;
-
-import com.google.common.net.MediaType;
+import java.util.List;
 
 /**
- * A thumbnail provider for image files.
+ * A Sling Model interface for retrieving the transformations available to a
+ * particular resource
  */
-@Component(service = ThumbnailProvider.class)
-public class ImageThumbnailProvider implements ThumbnailProvider {
-
-    @Override
-    public boolean applies(File file) {
-        return MediaType.parse(file.getContentType()).is(MediaType.ANY_IMAGE_TYPE);
-    }
-
-    @Override
-    public InputStream getThumbnail(File file) {
-        return file.getResource().adaptTo(InputStream.class);
-    }
+public interface TransformationManager {
 
+    /**
+     * Gets the transformations available to a particular resource based on the CA
+     * Configs
+     * 
+     * @return a list of transformations
+     */
+    List<Transformation> getTransformations();
 }
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/ThumbnailProvider.java b/transformer/src/main/java/org/apache/sling/cms/transformer/Transformer.java
similarity index 55%
copy from transformer/src/main/java/org/apache/sling/cms/transformer/ThumbnailProvider.java
copy to transformer/src/main/java/org/apache/sling/cms/transformer/Transformer.java
index decfade..9c5e997 100644
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/ThumbnailProvider.java
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/Transformer.java
@@ -17,31 +17,26 @@
 package org.apache.sling.cms.transformer;
 
 import java.io.IOException;
-import java.io.InputStream;
+import java.io.OutputStream;
 
-import org.apache.sling.cms.File;
+import org.apache.sling.api.resource.Resource;
 
 /**
- * Service for retrieving a thumbnail for the specified File object.
+ * Transforms a resource using the registered TransformationHandlers to invoke
+ * transformations on the file.
  */
-public interface ThumbnailProvider {
+public interface Transformer {
 
     /**
-     * Returns true if the ThumbnailProvider applies for the specified file.
+     * Transforms the resource
      * 
-     * @param file the file to check
-     * @return true if this ThumbnailProvider will create a thumbnail for this file,
-     *         false otherwise
+     * @param resource the resource to transform
+     * @param commands the commands to execute
+     * @param format   the format of the stream to return
+     * @param out      the OutputStream to which to write the transformed file
+     * @throws IOException an exception occurs transforming the resource
      */
-    boolean applies(File file);
+    void transform(Resource resource, Transformation transformation, OutputFileFormat format, OutputStream out)
+            throws IOException;
 
-    /**
-     * Get the thumbnail from the specified file.
-     * 
-     * @param file the file from which to retrieve the thumbnail
-     * @return the thumbnail
-     * @throws IOException an exception occurs retrieving the thumbnail
-     */
-    InputStream getThumbnail(File file) throws IOException;
-
-}
\ No newline at end of file
+}
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/CropHandler.java b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/CropHandler.java
index ce5d813..573a915 100644
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/CropHandler.java
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/CropHandler.java
@@ -19,6 +19,7 @@ package org.apache.sling.cms.transformer.internal;
 import java.io.IOException;
 import java.io.InputStream;
 
+import org.apache.sling.api.resource.Resource;
 import org.apache.sling.cms.transformer.TransformationHandler;
 import org.osgi.service.component.annotations.Component;
 
@@ -28,21 +29,25 @@ import net.coobird.thumbnailator.geometry.Positions;
 /**
  * A transformation handler to crop images
  */
-@Component(service = TransformationHandler.class)
+@Component(service = TransformationHandler.class, property = { TransformationHandler.HANDLER_RESOURCE_TYPE
+        + "=sling-cms/components/caconfig/transformationhandlers/crop" }, immediate = true)
 public class CropHandler implements TransformationHandler {
 
+    public static final String PN_POSITION = "position";
+
     @Override
-    public boolean applies(String command) {
-        return command.startsWith("crop-");
+    public String getResourceType() {
+        return "sling-cms/components/caconfig/transformationhandlers/crop";
     }
 
     @Override
-    public void handle(Builder<? extends InputStream> builder, String cmd) throws IOException {
+    public void handle(Builder<? extends InputStream> builder, Resource config) throws IOException {
+        String positionStr = config.getValueMap().get(PN_POSITION, "CENTER").toUpperCase();
         try {
-            Positions pos = Positions.valueOf(cmd.split("\\-")[1].toUpperCase());
+            Positions pos = Positions.valueOf(positionStr);
             builder.crop(pos);
         } catch (IllegalArgumentException e) {
-            throw new IOException("Unable to load crop position from " + cmd.split("\\-")[1], e);
+            throw new IOException("Unable to load crop position from " + positionStr, e);
         }
     }
 
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/ImageThumbnailProvider.java b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/ImageThumbnailProvider.java
index 516d8fc..79fc9bf 100644
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/ImageThumbnailProvider.java
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/ImageThumbnailProvider.java
@@ -17,7 +17,11 @@
 package org.apache.sling.cms.transformer.internal;
 
 import java.io.InputStream;
+import java.util.Optional;
 
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.cms.CMSConstants;
 import org.apache.sling.cms.File;
 import org.apache.sling.cms.transformer.ThumbnailProvider;
 import org.osgi.service.component.annotations.Component;
@@ -27,17 +31,20 @@ import com.google.common.net.MediaType;
 /**
  * A thumbnail provider for image files.
  */
-@Component(service = ThumbnailProvider.class)
+@Component(service = ThumbnailProvider.class, immediate = true)
 public class ImageThumbnailProvider implements ThumbnailProvider {
 
     @Override
-    public boolean applies(File file) {
-        return MediaType.parse(file.getContentType()).is(MediaType.ANY_IMAGE_TYPE);
+    public boolean applies(Resource resource) {
+        return (CMSConstants.NT_FILE.equals(resource.getResourceType())
+                || JcrConstants.NT_FILE.equals(resource.getResourceType()))
+                && Optional.ofNullable(resource.adaptTo(File.class))
+                        .map(f -> MediaType.parse(f.getContentType()).is(MediaType.ANY_IMAGE_TYPE)).orElse(false);
     }
 
     @Override
-    public InputStream getThumbnail(File file) {
-        return file.getResource().adaptTo(InputStream.class);
+    public InputStream getThumbnail(Resource resource) {
+        return resource.adaptTo(InputStream.class);
     }
 
 }
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/PdfThumbnailProvider.java b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/PdfThumbnailProvider.java
index 559e828..0230da9 100644
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/PdfThumbnailProvider.java
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/PdfThumbnailProvider.java
@@ -21,12 +21,16 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Optional;
 
 import javax.imageio.ImageIO;
 
+import org.apache.jackrabbit.JcrConstants;
 import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.pdfbox.rendering.ImageType;
 import org.apache.pdfbox.rendering.PDFRenderer;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.cms.CMSConstants;
 import org.apache.sling.cms.File;
 import org.apache.sling.cms.transformer.ThumbnailProvider;
 import org.osgi.service.component.annotations.Component;
@@ -36,17 +40,20 @@ import com.google.common.net.MediaType;
 /**
  * A thumbnail provider for PDF documents.
  */
-@Component(service = ThumbnailProvider.class)
+@Component(service = ThumbnailProvider.class, immediate = true)
 public class PdfThumbnailProvider implements ThumbnailProvider {
 
     @Override
-    public boolean applies(File file) {
-        return MediaType.PDF.is(MediaType.parse(file.getContentType()));
+    public boolean applies(Resource resource) {
+        return (CMSConstants.NT_FILE.equals(resource.getResourceType())
+                || JcrConstants.NT_FILE.equals(resource.getResourceType()))
+                && Optional.ofNullable(resource.adaptTo(File.class))
+                        .map(r -> MediaType.PDF.is(MediaType.parse(r.getContentType()))).orElse(false);
     }
 
     @Override
-    public InputStream getThumbnail(File file) throws IOException {
-        try (PDDocument document = PDDocument.load(file.getResource().adaptTo(InputStream.class))) {
+    public InputStream getThumbnail(Resource resource) throws IOException {
+        try (PDDocument document = PDDocument.load(resource.adaptTo(InputStream.class))) {
             PDFRenderer pdfRenderer = new PDFRenderer(document);
             BufferedImage bim = pdfRenderer.renderImageWithDPI(0, 300, ImageType.RGB);
             ByteArrayOutputStream os = new ByteArrayOutputStream();
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/RenditionCleaner.java b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/RenditionCleaner.java
new file mode 100644
index 0000000..d1e75ee
--- /dev/null
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/RenditionCleaner.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.cms.transformer.internal;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = EventHandler.class, property = {
+        EventConstants.EVENT_TOPIC + "=org/apache/sling/api/resource/Resource/CHANGED",
+        EventConstants.EVENT_FILTER + "=(&(resourceType=sling:FileContent))" })
+public class RenditionCleaner implements EventHandler {
+
+    private static final Logger log = LoggerFactory.getLogger(RenditionCleaner.class);
+
+    @Reference
+    private TransformationServiceUser transformationServiceUser;
+
+    @Override
+    public void handleEvent(Event event) {
+        try (ResourceResolver resolver = transformationServiceUser.getTransformationServiceUser()) {
+            Resource renditions = resolver.getResource(event.getProperty("path") + "/renditions");
+            if (renditions != null) {
+                resolver.delete(renditions);
+                resolver.commit();
+            }
+        } catch (LoginException | PersistenceException e) {
+            log.warn("Failed to remove renditions", e);
+        }
+    }
+
+}
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/SizeHandler.java b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/SizeHandler.java
index 1d8a2c3..79bd276 100644
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/SizeHandler.java
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/SizeHandler.java
@@ -19,34 +19,31 @@ package org.apache.sling.cms.transformer.internal;
 import java.io.IOException;
 import java.io.InputStream;
 
+import org.apache.sling.api.resource.Resource;
 import org.apache.sling.cms.transformer.TransformationHandler;
 import org.osgi.service.component.annotations.Component;
 
 import net.coobird.thumbnailator.Thumbnails.Builder;
 
-@Component(service = TransformationHandler.class)
+/**
+ * A transformer for resizing an image
+ */
+@Component(service = TransformationHandler.class, property = { TransformationHandler.HANDLER_RESOURCE_TYPE
+        + "=sling-cms/components/caconfig/transformationhandlers/size" }, immediate = true)
 public class SizeHandler implements TransformationHandler {
 
+    public static final String PN_HEIGHT = "height";
+    public static final String PN_WIDTH = "width";
+
     @Override
-    public boolean applies(String command) {
-        return command.startsWith("size-");
+    public String getResourceType() {
+        return "sling-cms/components/caconfig/transformationhandlers/size";
     }
 
     @Override
-    public void handle(Builder<? extends InputStream> builder, String cmd) throws IOException {
-        int width = -1;
-        try {
-            width = Integer.parseInt(cmd.split("\\-")[1], 10);
-        } catch (NumberFormatException nfe) {
-            throw new IOException("Failed to get width from " + cmd.split("\\-")[1]);
-        }
-
-        int height = -1;
-        try {
-            height = Integer.parseInt(cmd.split("\\-")[2], 10);
-        } catch (NumberFormatException nfe) {
-            throw new IOException("Failed to get height from " + cmd.split("\\-")[2]);
-        }
+    public void handle(Builder<? extends InputStream> builder, Resource config) throws IOException {
+        int width = config.getValueMap().get(PN_WIDTH, -1);
+        int height = config.getValueMap().get(PN_HEIGHT, -1);
         builder.size(width, height);
     }
 
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/SlideShowThumbnailProvider.java b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/SlideShowThumbnailProvider.java
index eef0eb1..e87371b 100644
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/SlideShowThumbnailProvider.java
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/SlideShowThumbnailProvider.java
@@ -25,14 +25,18 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
+import java.util.Optional;
 
 import javax.imageio.ImageIO;
 
 import org.apache.commons.io.output.ByteArrayOutputStream;
+import org.apache.jackrabbit.JcrConstants;
 import org.apache.poi.hslf.usermodel.HSLFSlideShow;
 import org.apache.poi.sl.usermodel.Slide;
 import org.apache.poi.sl.usermodel.SlideShow;
 import org.apache.poi.xslf.usermodel.XMLSlideShow;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.cms.CMSConstants;
 import org.apache.sling.cms.File;
 import org.apache.sling.cms.transformer.OutputFileFormat;
 import org.apache.sling.cms.transformer.ThumbnailProvider;
@@ -45,28 +49,32 @@ import com.google.common.net.MediaType;
 /**
  * Provides Thumbnails for Microsoft PPT and PPTX files.
  */
-@Component(service = ThumbnailProvider.class)
+@Component(service = ThumbnailProvider.class, immediate = true)
 public class SlideShowThumbnailProvider implements ThumbnailProvider {
 
     @Reference
     private DynamicClassLoaderManager dclm;
 
     @Override
-    public boolean applies(File file) {
-        MediaType mt = MediaType.parse(file.getContentType());
-        return mt.is(MediaType.MICROSOFT_POWERPOINT) || mt.is(MediaType.OOXML_PRESENTATION);
+    public boolean applies(Resource resource) {
+        return (CMSConstants.NT_FILE.equals(resource.getResourceType())
+                || JcrConstants.NT_FILE.equals(resource.getResourceType()))
+                && Optional.ofNullable(resource.adaptTo(File.class)).map(f -> {
+                    MediaType mt = MediaType.parse(f.getContentType());
+                    return mt.is(MediaType.MICROSOFT_POWERPOINT) || mt.is(MediaType.OOXML_PRESENTATION);
+                }).orElse(false);
     }
 
     @Override
-    public InputStream getThumbnail(File file) throws IOException {
+    public InputStream getThumbnail(Resource resource) throws IOException {
         if (dclm != null) {
             Thread.currentThread().setContextClassLoader(dclm.getDynamicClassLoader());
         }
 
         SlideShow<?, ?> ppt = null;
-        MediaType mt = MediaType.parse(file.getContentType());
+        MediaType mt = MediaType.parse(resource.adaptTo(File.class).getContentType());
         try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                InputStream is = file.getResource().adaptTo(InputStream.class)) {
+                InputStream is = resource.adaptTo(InputStream.class)) {
             if (mt.is(MediaType.MICROSOFT_POWERPOINT)) {
                 ppt = new HSLFSlideShow(is);
             } else {
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TikaFallbackProvider.java b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TikaFallbackProvider.java
index df23413..a95b7b5 100644
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TikaFallbackProvider.java
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TikaFallbackProvider.java
@@ -26,7 +26,9 @@ import java.io.InputStream;
 import javax.imageio.ImageIO;
 import javax.swing.JEditorPane;
 
-import org.apache.sling.cms.File;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.cms.CMSConstants;
 import org.apache.sling.cms.transformer.OutputFileFormat;
 import org.apache.sling.cms.transformer.ThumbnailProvider;
 import org.apache.tika.exception.TikaException;
@@ -41,24 +43,26 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.xml.sax.SAXException;
 
-@Component(service = ThumbnailProvider.class, property = { Constants.SERVICE_RANKING + "=" + Integer.MIN_VALUE })
+@Component(service = ThumbnailProvider.class, property = {
+        Constants.SERVICE_RANKING + "=" + Integer.MIN_VALUE }, immediate = true)
 public class TikaFallbackProvider implements ThumbnailProvider {
 
     private static final Logger log = LoggerFactory.getLogger(TikaFallbackProvider.class);
 
     @Override
-    public boolean applies(File file) {
-        return true;
+    public boolean applies(Resource resource) {
+        return (CMSConstants.NT_FILE.equals(resource.getResourceType())
+                || JcrConstants.NT_FILE.equals(resource.getResourceType()));
     }
 
     @Override
-    public InputStream getThumbnail(File file) throws IOException {
+    public InputStream getThumbnail(Resource resource) throws IOException {
 
-        log.info("Extracting content thumbnail from {}", file.getPath());
+        log.info("Extracting content thumbnail from {}", resource.getPath());
         try {
 
             log.debug("Extracting file contents");
-            InputStream is = file.getResource().adaptTo(InputStream.class);
+            InputStream is = resource.adaptTo(InputStream.class);
             Parser parser = new AutoDetectParser();
             BodyContentHandler handler = new BodyContentHandler();
             Metadata md = new Metadata();
@@ -78,7 +82,7 @@ public class TikaFallbackProvider implements ThumbnailProvider {
             ImageIO.write(image, OutputFileFormat.PNG.toString(), baos);
             return new ByteArrayInputStream(baos.toByteArray());
         } catch (SAXException | TikaException e) {
-            throw new IOException("Failed to generate thumbnail from " + file.getPath(), e);
+            throw new IOException("Failed to generate thumbnail from " + resource.getPath(), e);
         }
     }
 
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TransformServlet.java b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TransformServlet.java
index c01d59f..06a1715 100644
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TransformServlet.java
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TransformServlet.java
@@ -16,16 +16,33 @@
  */
 package org.apache.sling.cms.transformer.internal;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
 
+import javax.jcr.query.Query;
 import javax.servlet.Servlet;
 import javax.servlet.ServletException;
 
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.poi.util.IOUtils;
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
-import org.apache.sling.cms.transformer.FileThumbnailTransformer;
+import org.apache.sling.cms.CMSConstants;
 import org.apache.sling.cms.transformer.OutputFileFormat;
+import org.apache.sling.cms.transformer.Transformation;
+import org.apache.sling.cms.transformer.Transformer;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
@@ -45,21 +62,87 @@ public class TransformServlet extends SlingSafeMethodsServlet {
 
     private static final long serialVersionUID = -1513067546618762171L;
 
-    @Reference
-    private transient FileThumbnailTransformer transformer;
+    public static final String SERVICE_USER = "sling-cms-transformer";
+
+    private transient TransformationServiceUser transformationServiceUser;
+
+    private transient Transformer transformer;
 
     @Override
     protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
             throws ServletException, IOException {
+        log.trace("doGet");
+
+        String name = StringUtils.substringBeforeLast(request.getRequestPathInfo().getSuffix(), ".");
+        String format = StringUtils.substringAfterLast(request.getRequestPathInfo().getSuffix(), ".");
+        log.debug("Transforming resource: {} with transformation: {} to {}", request.getResource(), name, format);
         String original = response.getContentType();
-        try {
-            response.setContentType(OutputFileFormat.forRequest(request).getMimeType());
-            transformer.transformFile(request, response.getOutputStream());
+        try (ResourceResolver serviceResolver = transformationServiceUser.getTransformationServiceUser()) {
+            Transformation transformation = findTransformation(serviceResolver, name);
+            if (transformation != null) {
+                response.setContentType(OutputFileFormat.forRequest(request).getMimeType());
+                String expectedPath = "jcr:content/renditions/" + name + "." + format;
+                Resource rendition = request.getResource().getChild(expectedPath);
+                if (rendition != null) {
+                    log.debug("Using existing rendition {}", name);
+                    IOUtils.copy(rendition.adaptTo(InputStream.class), response.getOutputStream());
+                } else {
+                    log.debug("Creating new rendition {}", name);
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    transformer.transform(request.getResource(), transformation,
+                            OutputFileFormat.valueOf(format.toUpperCase()), baos);
+                    IOUtils.copy(new ByteArrayInputStream(baos.toByteArray()), response.getOutputStream());
+                    if (CMSConstants.NT_FILE.equals(request.getResource().getResourceType())) {
+                        Resource file = ResourceUtil.getOrCreateResource(serviceResolver,
+                                request.getResource().getPath() + "/" + expectedPath,
+                                Collections.singletonMap(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_FILE),
+                                JcrConstants.NT_UNSTRUCTURED, false);
+                        Map<String, Object> properties = new HashMap<>();
+                        properties.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+                        properties.put(JcrConstants.JCR_DATA, new ByteArrayInputStream(baos.toByteArray()));
+                        ResourceUtil.getOrCreateResource(serviceResolver,
+                                file.getPath() + "/" + JcrConstants.JCR_CONTENT, properties,
+                                JcrConstants.NT_UNSTRUCTURED, true);
+                    }
+
+                }
+            } else {
+                log.error("Exception transforming image: {} with transformation: {}", request.getResource(), name);
+                response.setContentType(original);
+                response.sendError(400, "Could not transform image with transformation: " + name);
+            }
         } catch (Exception e) {
-            log.error("Exception transforming image", e);
+            log.error("Exception rendering transformed resource", e);
             response.setContentType(original);
-            response.sendError(400, "Could not transform image with provided commands");
+            response.sendError(500, "Could not transform image with transformation: " + name);
+
         }
     }
 
+    protected Transformation findTransformation(ResourceResolver serviceResolver, String name) throws LoginException {
+        name = name.substring(1).replace("'", "''");
+        log.debug("Finding transformations with {}", name);
+
+        Iterator<Resource> transformations = serviceResolver.findResources(
+                "SELECT * FROM [nt:unstructured] WHERE ISDESCENDANTNODE([/conf]) AND [sling:resourceType]='sling-cms/components/caconfig/transformation' AND [name]='"
+                        + name + "'",
+                Query.JCR_SQL2);
+        if (transformations.hasNext()) {
+            Resource transformation = transformations.next();
+            return transformation.adaptTo(Transformation.class);
+        }
+
+        return null;
+    }
+
+    @Reference
+    public void setTransformationServiceUser(TransformationServiceUser transformationServiceUser) {
+        this.transformationServiceUser = transformationServiceUser;
+    }
+
+    @Reference
+    public void setTransformer(Transformer transformer) {
+        this.transformer = transformer;
+    }
+
 }
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TransformationServiceUser.java b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TransformationServiceUser.java
new file mode 100644
index 0000000..210729c
--- /dev/null
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TransformationServiceUser.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.cms.transformer.internal;
+
+import java.util.Collections;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * Service for retrieving the service user
+ */
+@Component(service = TransformationServiceUser.class)
+public class TransformationServiceUser {
+
+    public static final String SERVICE_USER = "sling-cms-transformer";
+
+    private ResourceResolverFactory resolverFactory;
+
+    public ResourceResolver getTransformationServiceUser() throws LoginException {
+        return resolverFactory
+                .getServiceResourceResolver(Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, SERVICE_USER));
+    }
+
+    @Reference
+    public void setResolverFactory(ResourceResolverFactory resolverFactory) {
+        this.resolverFactory = resolverFactory;
+    }
+}
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/FileThumbnailTransformerImpl.java b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TransformerImpl.java
similarity index 51%
rename from transformer/src/main/java/org/apache/sling/cms/transformer/internal/FileThumbnailTransformerImpl.java
rename to transformer/src/main/java/org/apache/sling/cms/transformer/internal/TransformerImpl.java
index e50dfd7..1fb7477 100644
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/FileThumbnailTransformerImpl.java
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TransformerImpl.java
@@ -19,16 +19,15 @@ package org.apache.sling.cms.transformer.internal;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 
-import org.apache.commons.lang3.StringUtils;
-import org.apache.sling.api.SlingHttpServletRequest;
-import org.apache.sling.cms.File;
-import org.apache.sling.cms.transformer.FileThumbnailTransformer;
+import org.apache.sling.api.resource.Resource;
 import org.apache.sling.cms.transformer.OutputFileFormat;
 import org.apache.sling.cms.transformer.ThumbnailProvider;
+import org.apache.sling.cms.transformer.Transformation;
 import org.apache.sling.cms.transformer.TransformationHandler;
+import org.apache.sling.cms.transformer.Transformer;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.component.annotations.ReferenceCardinality;
@@ -41,10 +40,10 @@ import com.google.common.collect.Lists;
 import net.coobird.thumbnailator.Thumbnails;
 import net.coobird.thumbnailator.Thumbnails.Builder;
 
-@Component(service = FileThumbnailTransformer.class)
-public class FileThumbnailTransformerImpl implements FileThumbnailTransformer {
+@Component(service = Transformer.class)
+public class TransformerImpl implements Transformer {
 
-    private static final Logger log = LoggerFactory.getLogger(FileThumbnailTransformerImpl.class);
+    private static final Logger log = LoggerFactory.getLogger(TransformerImpl.class);
 
     @Reference(cardinality = ReferenceCardinality.AT_LEAST_ONE, policyOption = ReferencePolicyOption.GREEDY)
     private List<TransformationHandler> handlers;
@@ -52,17 +51,27 @@ public class FileThumbnailTransformerImpl implements FileThumbnailTransformer {
     @Reference(cardinality = ReferenceCardinality.AT_LEAST_ONE, policyOption = ReferencePolicyOption.GREEDY)
     private List<ThumbnailProvider> thumbnailProviders;
 
-    /**
-     * @return the handlers
-     */
+    public void addThumbnailProvider(ThumbnailProvider thumbnailProvider) {
+        if (thumbnailProviders == null) {
+            thumbnailProviders = new ArrayList<>();
+        }
+        this.thumbnailProviders.add(thumbnailProvider);
+    }
+
+    public void addTransformationHandler(TransformationHandler handler) {
+        if (handlers == null) {
+            handlers = new ArrayList<>();
+        }
+        handlers.add(handler);
+    }
+
     public List<TransformationHandler> getHandlers() {
         return handlers;
     }
 
-    private ThumbnailProvider getThumbnailProvider(File file) throws IOException {
-        return Lists.reverse(thumbnailProviders).stream().filter(tp -> tp.applies(file))
-                .findFirst()
-                .orElseThrow(() -> new IOException("Unable to find thumbnail provider for: " + file.getPath()));
+    private ThumbnailProvider getThumbnailProvider(Resource resource) throws IOException {
+        return Lists.reverse(thumbnailProviders).stream().filter(tp -> tp.applies(resource)).findFirst()
+                .orElseThrow(() -> new IOException("Unable to find thumbnail provider for: " + resource.getPath()));
     }
 
     /**
@@ -72,54 +81,30 @@ public class FileThumbnailTransformerImpl implements FileThumbnailTransformer {
         return thumbnailProviders;
     }
 
-    @Override
-    public TransformationHandler getTransformationHandler(String command) {
-        return handlers.stream().filter(h -> h.applies(command)).findFirst().orElse(null);
-    }
-
-    /**
-     * @param handlers the handlers to set
-     */
-    public void setHandlers(List<TransformationHandler> handlers) {
-        this.handlers = handlers;
-    }
-
-    /**
-     * @param thumbnailProviders the thumbnailProviders to set
-     */
-    public void setThumbnailProviders(List<ThumbnailProvider> thumbnailProviders) {
-        this.thumbnailProviders = thumbnailProviders;
+    public TransformationHandler getTransformationHandler(String resourceType) {
+        return handlers.stream().filter(h -> resourceType.equals(h.getResourceType())).findFirst().orElse(null);
     }
 
     @Override
-    public void transformFile(File file, String[] commands, OutputFileFormat format, OutputStream out)
+    public void transform(Resource resource, Transformation transformation, OutputFileFormat format, OutputStream out)
             throws IOException {
-        ThumbnailProvider provider = getThumbnailProvider(file);
-        log.debug("Using thumbnail provider {} for file {}", provider, file);
-        Builder<? extends InputStream> builder = Thumbnails.of(provider.getThumbnail(file));
-        for (String command : commands) {
-            if (StringUtils.isNotBlank(command)) {
-                log.debug("Handling command: {}", command);
-                TransformationHandler handler = getTransformationHandler(command);
-                if (handler != null) {
-                    log.debug("Invoking handler {} for command {}", handler.getClass().getCanonicalName(), command);
-                    handler.handle(builder, command);
-                } else {
-                    log.info("No handler found for: {}", command);
-                }
+        ThumbnailProvider provider = getThumbnailProvider(resource);
+        log.debug("Using thumbnail provider {} for resource {}", provider, resource);
+        Builder<? extends InputStream> builder = Thumbnails.of(provider.getThumbnail(resource));
+        for (Resource config : transformation.getHandlers()) {
+            log.debug("Handling command: {}", config);
+            TransformationHandler handler = getTransformationHandler(config.getResourceType());
+            if (handler != null) {
+                log.debug("Invoking handler {} for command {}", handler.getClass().getCanonicalName(),
+                        config.getName());
+                handler.handle(builder, config);
+            } else {
+                log.info("No handler found for: {}", config.getName());
             }
         }
         builder.outputFormat(format.toString());
         builder.toOutputStream(out);
-    }
 
-    @Override
-    public void transformFile(SlingHttpServletRequest request, OutputStream out) throws IOException {
-        OutputFileFormat fileFormat = OutputFileFormat.forRequest(request);
-        transformFile(request.getResource().adaptTo(File.class),
-                Optional.ofNullable(request.getRequestPathInfo().getSuffix())
-                        .map(s -> StringUtils.substringBeforeLast(s, ".")).map(s -> s.split("/")).orElse(new String[0]),
-                fileFormat, out);
     }
 
 }
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TransformerWebConsole.java b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TransformerWebConsole.java
index 185699b..4422894 100644
--- a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TransformerWebConsole.java
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/TransformerWebConsole.java
@@ -28,9 +28,9 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.apache.felix.webconsole.AbstractWebConsolePlugin;
 import org.apache.felix.webconsole.WebConsoleConstants;
-import org.apache.sling.cms.transformer.FileThumbnailTransformer;
 import org.apache.sling.cms.transformer.ThumbnailProvider;
 import org.apache.sling.cms.transformer.TransformationHandler;
+import org.apache.sling.cms.transformer.Transformer;
 import org.osgi.framework.Constants;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
@@ -55,7 +55,7 @@ public class TransformerWebConsole extends AbstractWebConsolePlugin {
 
     @SuppressWarnings({ "squid:S2078", "squid:S2226" }) // ignore since this field is is injected by OSGi
     @Reference
-    private transient FileThumbnailTransformer fileThumbnailTransformer;
+    private transient Transformer transformer;
 
     @Override
     public String getTitle() {
@@ -75,16 +75,15 @@ public class TransformerWebConsole extends AbstractWebConsolePlugin {
         pw.println("<pre>");
         pw.println("Registered Thumbnail Providers");
         pw.println("========================");
-        List<ThumbnailProvider> providers = ((FileThumbnailTransformerImpl) fileThumbnailTransformer)
-                .getThumbnailProviders();
+        List<ThumbnailProvider> providers = ((TransformerImpl) transformer).getThumbnailProviders();
         Lists.reverse(providers).forEach(p -> pw.println(p.getClass().getName()));
         pw.println("</pre><br/>");
         pw.println("<pre>");
         pw.println("Registered Transformation Handlers");
         pw.println("========================");
 
-        List<TransformationHandler> handlers = ((FileThumbnailTransformerImpl) fileThumbnailTransformer).getHandlers();
-        handlers.forEach(h -> pw.println(h.getClass().getName()));
+        List<TransformationHandler> handlers = ((TransformerImpl) transformer).getHandlers();
+        handlers.forEach(h -> pw.println(h.getResourceType() + "=" + h.getClass().getCanonicalName()));
         pw.println("</pre>");
         pw.println("</div>");
     }
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/models/TransformationImpl.java b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/models/TransformationImpl.java
new file mode 100644
index 0000000..433ac67
--- /dev/null
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/models/TransformationImpl.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.transformer.internal.models;
+
+import java.util.List;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.cms.transformer.Transformation;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.ChildResource;
+import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
+
+@Model(adaptables = Resource.class, adapters = Transformation.class)
+public class TransformationImpl implements Transformation {
+
+    @ChildResource
+    private List<Resource> handlers;
+
+    @ValueMapValue
+    private String name;
+
+    @Override
+    public List<Resource> getHandlers() {
+        return handlers;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    public void sethandlers(List<Resource> handlers) {
+        this.handlers = handlers;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+}
diff --git a/transformer/src/main/java/org/apache/sling/cms/transformer/internal/models/TransformationManagerImpl.java b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/models/TransformationManagerImpl.java
new file mode 100644
index 0000000..44a72bd
--- /dev/null
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/internal/models/TransformationManagerImpl.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.transformer.internal.models;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.caconfig.resource.ConfigurationResourceResolver;
+import org.apache.sling.cms.transformer.Transformation;
+import org.apache.sling.cms.transformer.TransformationManager;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.OSGiService;
+
+@Model(adaptables = Resource.class, adapters = TransformationManager.class)
+public class TransformationManagerImpl implements TransformationManager {
+
+    private Resource resource;
+
+    @OSGiService
+    private ConfigurationResourceResolver configResourceResolver;
+
+    public TransformationManagerImpl(Resource resource) {
+        this.resource = resource;
+    }
+
+    @Override
+    public List<Transformation> getTransformations() {
+        Collection<Resource> transformationResources = configResourceResolver.getResourceCollection(resource, "file",
+                "transformations");
+        return transformationResources.stream().map(r -> r.adaptTo(Transformation.class)).collect(Collectors.toList());
+    }
+
+}
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp b/transformer/src/main/java/org/apache/sling/cms/transformer/package-info.java
similarity index 78%
copy from ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp
copy to transformer/src/main/java/org/apache/sling/cms/transformer/package-info.java
index 69e44fa..0594c64 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp
+++ b/transformer/src/main/java/org/apache/sling/cms/transformer/package-info.java
@@ -1,4 +1,4 @@
-<%-- /*
+/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -15,8 +15,13 @@
  * KIND, either express or implied.  See the License for the
  * specific language governing permissions and limitations
  * under the License.
- */ --%>
-<%@include file="/libs/sling-cms/global.jsp"%>
-<c:if test="${slingRequest.requestPathInfo.suffixResource != null}">
-    <sling:include resource="${slingRequest.requestPathInfo.suffixResource}" />
-</c:if>
\ No newline at end of file
+ */
+
+/**
+ * Package with all of the common models used to access content in the Sling
+ * reference CMS
+ *
+ * @since 0.10.0
+ */
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.cms.transformer;
diff --git a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/CropHandlerTest.java b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/CropHandlerTest.java
index edefd0e..7f043ce 100644
--- a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/CropHandlerTest.java
+++ b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/CropHandlerTest.java
@@ -17,25 +17,28 @@
 package org.apache.sling.cms.transformer.internal;
 
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Collections;
 
-import org.apache.sling.cms.transformer.internal.CropHandler;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.testing.resourceresolver.MockResource;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mockito;
 
 import net.coobird.thumbnailator.Thumbnails;
 import net.coobird.thumbnailator.Thumbnails.Builder;
 
 public class CropHandlerTest {
-    
+
     private Builder<? extends InputStream> builder;
     private CropHandler cropper;
 
-    @Before 
+    @Before
     public void init() {
         builder = Thumbnails.of(getClass().getClassLoader().getResourceAsStream("apache.png"));
         builder.size(200, 200);
@@ -43,29 +46,31 @@ public class CropHandlerTest {
     }
 
     @Test
-    public void testApplies() {
-        assertTrue(cropper.applies("crop-CENTER"));
-    }
-
-    @Test
     public void testCrop() throws IOException {
-        cropper.handle(builder, "crop-CENTER");
+        Resource config = new MockResource("/conf", Collections.singletonMap(CropHandler.PN_POSITION, "CENTER"),
+                Mockito.mock(ResourceResolver.class));
+        cropper.handle(builder, config);
         assertNotNull(builder.asBufferedImage());
     }
-    
 
     @Test
     public void testCropLower() throws IOException {
-        cropper.handle(builder, "crop-center");
+
+        Resource config = new MockResource("/conf", Collections.singletonMap(CropHandler.PN_POSITION, "center"),
+                Mockito.mock(ResourceResolver.class));
+        cropper.handle(builder, config);
         assertNotNull(builder.asBufferedImage());
     }
-    
+
     @Test
     public void testInvalidParam() throws IOException {
         try {
-            cropper.handle(builder, "crop-CENTERZ");
+
+            Resource config = new MockResource("/conf", Collections.singletonMap(CropHandler.PN_POSITION, "centerz"),
+                    Mockito.mock(ResourceResolver.class));
+            cropper.handle(builder, config);
             fail();
-        }catch(IOException e) {
+        } catch (IOException e) {
         }
     }
 
diff --git a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/FileThumbnailTransformerImplTest.java b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/FileThumbnailTransformerImplTest.java
deleted file mode 100644
index 0c391d3..0000000
--- a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/FileThumbnailTransformerImplTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sling.cms.transformer.internal;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-
-import org.apache.poi.util.IOUtils;
-import org.apache.sling.cms.transformer.FileThumbnailTransformer;
-import org.apache.sling.cms.transformer.ThumbnailProvider;
-import org.apache.sling.cms.transformer.TransformationHandler;
-import org.apache.sling.cms.transformer.helpers.SlingCMSContextHelper;
-import org.apache.sling.cms.transformer.internal.CropHandler;
-import org.apache.sling.cms.transformer.internal.FileThumbnailTransformerImpl;
-import org.apache.sling.cms.transformer.internal.ImageThumbnailProvider;
-import org.apache.sling.cms.transformer.internal.PdfThumbnailProvider;
-import org.apache.sling.cms.transformer.internal.SizeHandler;
-import org.apache.sling.testing.mock.sling.junit.SlingContext;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-import com.google.common.collect.Lists;
-
-public class FileThumbnailTransformerImplTest {
-    @Rule
-    public final SlingContext context = new SlingContext();
-    private FileThumbnailTransformer transformer;
-
-    @Before
-    public void init() {
-        SlingCMSContextHelper.initContext(context);
-        transformer = new FileThumbnailTransformerImpl();
-        ((FileThumbnailTransformerImpl) transformer)
-                .setHandlers(Lists.asList(new CropHandler(), new TransformationHandler[] { new SizeHandler() }));
-        ((FileThumbnailTransformerImpl) transformer).setThumbnailProviders(
-                Lists.asList(new ImageThumbnailProvider(), new ThumbnailProvider[] { new PdfThumbnailProvider() }));
-    }
-
-    @Test
-    public void testGetHandler() {
-        assertNotNull(transformer.getTransformationHandler("size-200-200"));
-        assertNull(transformer.getTransformationHandler("sizey-200-200"));
-    }
-
-
-    @Test
-    public void testImageThumbnail() throws IOException {
-        context.requestPathInfo().setSuffix("/not-a-command/size-200-200/crop-center.png");
-        context.currentResource("/content/apache/sling-apache-org/index/apache.png");
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        transformer.transformFile(context.request(), baos);
-        assertNotNull(baos);
-        
-        IOUtils.copy(new ByteArrayInputStream(baos.toByteArray()), new File("src/test/resources/thumbnail.png"));
-    }
-
-}
diff --git a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/ImageThumbnailProviderTest.java b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/ImageThumbnailProviderTest.java
index ca89c09..05e85c6 100644
--- a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/ImageThumbnailProviderTest.java
+++ b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/ImageThumbnailProviderTest.java
@@ -24,9 +24,8 @@ import static org.junit.Assert.assertTrue;
 import java.io.IOException;
 
 import org.apache.commons.io.IOUtils;
-import org.apache.sling.cms.File;
+import org.apache.sling.api.resource.Resource;
 import org.apache.sling.cms.transformer.helpers.SlingCMSContextHelper;
-import org.apache.sling.cms.transformer.internal.ImageThumbnailProvider;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.junit.Before;
 import org.junit.Rule;
@@ -41,18 +40,16 @@ public class ImageThumbnailProviderTest {
     @Rule
     public final SlingContext context = new SlingContext();
 
-    private File imageFile;
-    private File pdfFile;
+    private Resource imageFile;
+    private Resource pdfFile;
 
     @Before
     public void init() {
         SlingCMSContextHelper.initContext(context);
 
-        imageFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/apache.png")
-                .adaptTo(File.class);
+        imageFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/apache.png");
 
-        pdfFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/sling.pdf")
-                .adaptTo(File.class);
+        pdfFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/sling.pdf");
     }
 
     @Test
diff --git a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/PdfThumbnailProviderTest.java b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/PdfThumbnailProviderTest.java
index 09eb986..2bf6f4a 100644
--- a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/PdfThumbnailProviderTest.java
+++ b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/PdfThumbnailProviderTest.java
@@ -22,9 +22,8 @@ import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
 
-import org.apache.sling.cms.File;
+import org.apache.sling.api.resource.Resource;
 import org.apache.sling.cms.transformer.helpers.SlingCMSContextHelper;
-import org.apache.sling.cms.transformer.internal.PdfThumbnailProvider;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.junit.Before;
 import org.junit.Rule;
@@ -39,16 +38,14 @@ public class PdfThumbnailProviderTest {
     @Rule
     public final SlingContext context = new SlingContext();
 
-    private File imageFile;
-    private File pdfFile;
+    private Resource imageFile;
+    private Resource pdfFile;
 
     @Before
     public void init() {
         SlingCMSContextHelper.initContext(context);
-        imageFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/apache.png")
-                .adaptTo(File.class);
-        pdfFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/sling.pdf")
-                .adaptTo(File.class);
+        imageFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/apache.png");
+        pdfFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/sling.pdf");
     }
 
     @Test
diff --git a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/SizeHandlerTest.java b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/SizeHandlerTest.java
index e046f1f..55e2d51 100644
--- a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/SizeHandlerTest.java
+++ b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/SizeHandlerTest.java
@@ -17,52 +17,64 @@
 package org.apache.sling.cms.transformer.internal;
 
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
 
-import org.apache.sling.cms.transformer.internal.SizeHandler;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.testing.resourceresolver.MockResource;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mockito;
 
 import net.coobird.thumbnailator.Thumbnails;
 import net.coobird.thumbnailator.Thumbnails.Builder;
 
 public class SizeHandlerTest {
-    
+
     private Builder<? extends InputStream> builder;
     private SizeHandler sizer;
 
-    @Before 
+    @Before
     public void init() {
         builder = Thumbnails.of(getClass().getClassLoader().getResourceAsStream("apache.png"));
         sizer = new SizeHandler();
     }
 
     @Test
-    public void testApplies() {
-        assertTrue(sizer.applies("size-200-200"));
-    }
-
-    @Test
     public void testResize() throws IOException {
-        sizer.handle(builder, "size-200-200");
+
+        Map<String, Object> config = new HashMap<>();
+        config.put(SizeHandler.PN_WIDTH, 200);
+        config.put(SizeHandler.PN_HEIGHT, 200);
+        sizer.handle(builder, new MockResource("/conf", config, Mockito.mock(ResourceResolver.class)));
         assertNotNull(builder.asBufferedImage());
     }
-    
+
     @Test
-    public void testInvalidParam() throws IOException {
+    public void testInvalidWidth() throws IOException {
         try {
-            sizer.handle(builder, "size-k-200");
+            Map<String, Object> config = new HashMap<>();
+            config.put(SizeHandler.PN_WIDTH, "K");
+            config.put(SizeHandler.PN_HEIGHT, 200);
+            sizer.handle(builder, new MockResource("/conf", config, Mockito.mock(ResourceResolver.class)));
             fail();
-        }catch(IOException e) {
+        } catch (IOException | IllegalArgumentException e) {
         }
+    }
+
+    @Test
+    public void testInvalidHeight() throws IOException {
         try {
-            sizer.handle(builder, "size-222-h");
+            Map<String, Object> config = new HashMap<>();
+            config.put(SizeHandler.PN_WIDTH, 200);
+            config.put(SizeHandler.PN_HEIGHT, "h");
+            sizer.handle(builder, new MockResource("/conf", config, Mockito.mock(ResourceResolver.class)));
             fail();
-        }catch(IOException e) {
+        } catch (IOException | IllegalArgumentException e) {
         }
     }
 
diff --git a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/SlideShowThumbnailProviderTest.java b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/SlideShowThumbnailProviderTest.java
index 9111680..e4a059b 100644
--- a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/SlideShowThumbnailProviderTest.java
+++ b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/SlideShowThumbnailProviderTest.java
@@ -22,10 +22,9 @@ import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
 
-import org.apache.sling.cms.File;
+import org.apache.sling.api.resource.Resource;
 import org.apache.sling.cms.transformer.ThumbnailProvider;
 import org.apache.sling.cms.transformer.helpers.SlingCMSContextHelper;
-import org.apache.sling.cms.transformer.internal.SlideShowThumbnailProvider;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.junit.Before;
 import org.junit.Rule;
@@ -40,24 +39,19 @@ public class SlideShowThumbnailProviderTest {
     @Rule
     public final SlingContext context = new SlingContext();
 
-    private File docxFile;
-    private File pptFile;
-    private File pptxFile;
+    private Resource docxFile;
+    private Resource pptFile;
+    private Resource pptxFile;
 
     private ThumbnailProvider provider;
 
     @Before
     public void init() {
         SlingCMSContextHelper.initContext(context);
-        docxFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/Sling.docx")
-                .adaptTo(File.class);
-        pptxFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/Sling.pptx")
-                .adaptTo(File.class);
-        pptFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/Sling.ppt")
-                .adaptTo(File.class);
-
+        docxFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/Sling.docx");
+        pptxFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/Sling.pptx");
+        pptFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/Sling.ppt");
         provider = new SlideShowThumbnailProvider();
-
     }
 
     @Test
diff --git a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/TikaFallbackProviderTest.java b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/TikaFallbackProviderTest.java
index 45cc645..167a567 100644
--- a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/TikaFallbackProviderTest.java
+++ b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/TikaFallbackProviderTest.java
@@ -20,9 +20,8 @@ import static org.junit.Assert.assertNotNull;
 
 import java.io.IOException;
 
-import org.apache.sling.cms.File;
+import org.apache.sling.api.resource.Resource;
 import org.apache.sling.cms.transformer.helpers.SlingCMSContextHelper;
-import org.apache.sling.cms.transformer.internal.TikaFallbackProvider;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.junit.Before;
 import org.junit.Rule;
@@ -37,13 +36,12 @@ public class TikaFallbackProviderTest {
     @Rule
     public final SlingContext context = new SlingContext();
 
-    private File docxFile;
+    private Resource docxFile;
 
     @Before
     public void init() {
         SlingCMSContextHelper.initContext(context);
-        docxFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/Sling.docx")
-                .adaptTo(File.class);
+        docxFile = context.resourceResolver().getResource("/content/apache/sling-apache-org/index/Sling.docx");
     }
 
     @Test
diff --git a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/TransformServletTest.java b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/TransformServletTest.java
index 35c6324..5191f8b 100644
--- a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/TransformServletTest.java
+++ b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/TransformServletTest.java
@@ -20,48 +20,92 @@ import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 import javax.servlet.ServletException;
 
 import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.reflect.FieldUtils;
-import org.apache.sling.cms.transformer.ThumbnailProvider;
-import org.apache.sling.cms.transformer.TransformationHandler;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
 import org.apache.sling.cms.transformer.helpers.SlingCMSContextHelper;
-import org.apache.sling.cms.transformer.internal.CropHandler;
-import org.apache.sling.cms.transformer.internal.FileThumbnailTransformerImpl;
-import org.apache.sling.cms.transformer.internal.ImageThumbnailProvider;
-import org.apache.sling.cms.transformer.internal.PdfThumbnailProvider;
-import org.apache.sling.cms.transformer.internal.SizeHandler;
-import org.apache.sling.cms.transformer.internal.TransformServlet;
+import org.apache.sling.cms.transformer.internal.models.TransformationImpl;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.apache.sling.testing.resourceresolver.MockResource;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.Mockito;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.collect.Lists;
-
 public class TransformServletTest {
 
     private static final Logger log = LoggerFactory.getLogger(TransformServletTest.class);
-    
-    private TransformServlet ts = new TransformServlet();;
+
+    private TransformServlet ts = new TransformServlet();
 
     @Rule
     public final SlingContext context = new SlingContext();
 
     @Before
-    public void init() throws IllegalAccessException {
+    public void init() throws IllegalAccessException, LoginException {
         SlingCMSContextHelper.initContext(context);
 
-        FileThumbnailTransformerImpl transformer = new FileThumbnailTransformerImpl();
-        transformer.setHandlers(Lists.asList(new CropHandler(), new TransformationHandler[] { new SizeHandler() }));
-        transformer.setThumbnailProviders(
-                Lists.asList(new ImageThumbnailProvider(), new ThumbnailProvider[] { new PdfThumbnailProvider() }));
-        
-        FieldUtils.writeDeclaredField(ts,"transformer", transformer, true);
+        ResourceResolverFactory factory = Mockito.mock(ResourceResolverFactory.class);
+        ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+
+        TransformationImpl transformation = new TransformationImpl();
+        List<Resource> handlers = new ArrayList<>();
+        Map<String, Object> size = new HashMap<>();
+        size.put(SizeHandler.PN_WIDTH, 200);
+        size.put(SizeHandler.PN_HEIGHT, 200);
+        size.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, "sling-cms/components/caconfig/transformationhandlers/size");
+        handlers.add(new MockResource("/conf", size, Mockito.mock(ResourceResolver.class)));
+
+        Map<String, Object> crop = new HashMap<>();
+        crop.put(CropHandler.PN_POSITION, "center");
+        crop.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, "sling-cms/components/caconfig/transformationhandlers/crop");
+        handlers.add(new MockResource("/conf", crop, Mockito.mock(ResourceResolver.class)));
+
+        transformation.sethandlers(handlers);
+
+        Mockito.when(resolver.findResources(Mockito.anyString(), Mockito.anyString())).thenAnswer((ans) -> {
+            List<Resource> resources = new ArrayList<>();
+            if (ans.getArgument(0, String.class).contains("test'")) {
+                Resource resource = Mockito.mock(Resource.class);
+                Mockito.when(resource.adaptTo(Mockito.any())).thenReturn(transformation);
+                resources.add(resource);
+            }
+            return resources.iterator();
+        });
+
+        Mockito.when(factory.getServiceResourceResolver(Mockito.any())).thenReturn(resolver);
+        TransformationServiceUser tsu = new TransformationServiceUser();
+        tsu.setResolverFactory(factory);
+        ts.setTransformationServiceUser(tsu);
+
+        TransformerImpl transformer = new TransformerImpl();
+        ((TransformerImpl) transformer).addTransformationHandler(new CropHandler() {
+            public String getResourceType() {
+                return "sling-cms/components/caconfig/transformationhandlers/crop";
+            }
+        });
+        ((TransformerImpl) transformer).addTransformationHandler(new SizeHandler() {
+            public String getResourceType() {
+                return "sling-cms/components/caconfig/transformationhandlers/size";
+            }
+        });
+
+        ((TransformerImpl) transformer).addThumbnailProvider(new ImageThumbnailProvider());
+        ((TransformerImpl) transformer).addThumbnailProvider(new PdfThumbnailProvider());
+
+        ts.setTransformer(transformer);
+
     }
 
     @Test
@@ -69,7 +113,7 @@ public class TransformServletTest {
         log.info("testContentTypes");
 
         context.currentResource("/content/apache/sling-apache-org/index/apache.png");
-        context.requestPathInfo().setSuffix("/size-200-200/crop-center.png");
+        context.requestPathInfo().setSuffix("/test.png");
         context.requestPathInfo().setExtension("transform");
 
         ts.doGet(context.request(), context.response());
@@ -83,7 +127,7 @@ public class TransformServletTest {
         log.info("testContentTypes");
 
         context.currentResource("/content/apache/sling-apache-org/index/apache.png");
-        context.requestPathInfo().setSuffix("/size-200-200/crop-left.png");
+        context.requestPathInfo().setSuffix("/test2.png");
         context.requestPathInfo().setExtension("transform");
 
         ts.doGet(context.request(), context.response());
diff --git a/transformer/src/test/java/org/apache/sling/cms/transformer/internal/TransformerImplTest.java b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/TransformerImplTest.java
new file mode 100644
index 0000000..aa6b4d3
--- /dev/null
+++ b/transformer/src/test/java/org/apache/sling/cms/transformer/internal/TransformerImplTest.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.transformer.internal;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.poi.util.IOUtils;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.transformer.OutputFileFormat;
+import org.apache.sling.cms.transformer.Transformer;
+import org.apache.sling.cms.transformer.helpers.SlingCMSContextHelper;
+import org.apache.sling.cms.transformer.internal.models.TransformationImpl;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.apache.sling.testing.resourceresolver.MockResource;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class TransformerImplTest {
+    @Rule
+    public final SlingContext context = new SlingContext();
+    private Transformer transformer;
+
+    @Before
+    public void init() {
+        SlingCMSContextHelper.initContext(context);
+        transformer = new TransformerImpl();
+
+        ((TransformerImpl) transformer).addTransformationHandler(new CropHandler() {
+            public String getResourceType() {
+                return "sling-cms/components/caconfig/transformationhandlers/crop";
+            }
+        });
+        ((TransformerImpl) transformer).addTransformationHandler(new SizeHandler() {
+            public String getResourceType() {
+                return "sling-cms/components/caconfig/transformationhandlers/size";
+            }
+        });
+
+        ((TransformerImpl) transformer).addThumbnailProvider(new ImageThumbnailProvider());
+        ((TransformerImpl) transformer).addThumbnailProvider(new PdfThumbnailProvider());
+    }
+
+    @Test
+    public void testImageThumbnail() throws IOException {
+        context.currentResource("/content/apache/sling-apache-org/index/apache.png");
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        TransformationImpl transformation = new TransformationImpl();
+        List<Resource> handlers = new ArrayList<>();
+
+        Map<String, Object> size = new HashMap<>();
+        size.put(SizeHandler.PN_WIDTH, 200);
+        size.put(SizeHandler.PN_HEIGHT, 200);
+        size.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, "sling-cms/components/caconfig/transformationhandlers/size");
+        handlers.add(new MockResource("/conf", size, Mockito.mock(ResourceResolver.class)));
+
+        Map<String, Object> crop = new HashMap<>();
+        crop.put(CropHandler.PN_POSITION, "center");
+        crop.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, "sling-cms/components/caconfig/transformationhandlers/crop");
+        handlers.add(new MockResource("/conf", crop, Mockito.mock(ResourceResolver.class)));
+
+        transformation.sethandlers(handlers);
+        transformer.transform(context.currentResource(), transformation, OutputFileFormat.PNG, baos);
+        assertNotNull(baos);
+
+        IOUtils.copy(new ByteArrayInputStream(baos.toByteArray()), new File("src/test/resources/thumbnail.png"));
+    }
+
+}
diff --git a/transformer/src/test/resources/content.json b/transformer/src/test/resources/content.json
index 82e8ffc..b7c79ed 100644
--- a/transformer/src/test/resources/content.json
+++ b/transformer/src/test/resources/content.json
@@ -93,16 +93,22 @@
                             "sling:resourceType": "sling-cms/components/general/richtext"
                         }
                     }
+                },
+                "apache.png": {
+                    "jcr:primaryType": "sling:File"
+                },
+                "sling.pdf": {
+                    "jcr:primaryType": "sling:File"
+                },
+                "Sling.docx": {
+                    "jcr:primaryType": "sling:File"
+                },
+                "Sling.pptx": {
+                    "jcr:primaryType": "sling:File"
+                },
+                "Sling.ppt": {
+                    "jcr:primaryType": "sling:File"
                 }
-            },
-            "apache.png": {
-                "jcr:primaryType": "sling:File"
-            },
-            "sling.pdf": {
-                "jcr:primaryType": "sling:File"
-            },
-            "sling.docx": {
-                "jcr:primaryType": "sling:File"
             }
         }
     }
diff --git a/ui/src/main/frontend/js/cms.nav.js b/ui/src/main/frontend/js/cms.nav.js
index bb7bf01..98ed2b3 100644
--- a/ui/src/main/frontend/js/cms.nav.js
+++ b/ui/src/main/frontend/js/cms.nav.js
@@ -19,6 +19,11 @@
 /* eslint-env browser, es6 */
 (function (rava) {
     'use strict';
+    
+    var urlParams = new URLSearchParams(window.location.search),
+        resourceParam = urlParams.get('resource'),
+        searchParam = urlParams.get('search');
+    
     rava.bind(".navbar-burger", {
         events: {
             click: function () {
@@ -28,4 +33,67 @@
             }
         }
     });
+    
+    rava.bind('.layout-switch select', {
+        events: {
+            change: function () {
+                window.location = this.value;
+            }
+        }
+    });
+    
+
+    rava.bind('.contentnav', {
+        callbacks : {
+            created: function () {
+                let cnav = this;
+                var search = cnav.querySelector('input[name=search]'),
+                    filter = function (event) {
+                        event.stopPropagation();
+                        event.preventDefault();
+                        var value = search.value.toLowerCase();
+                        cnav.querySelectorAll('.contentnav__item').forEach(function (item) {
+                            if (item.innerText.toLowerCase().indexOf(value) === -1 && !item.querySelector('*[data-value="' + resourceParam + '"]')) {
+                                item.classList.add('is-hidden');
+                            } else {
+                                item.classList.remove('is-hidden');
+                            }
+                        });
+                    };
+                search.addEventListener('keyup', filter);
+                search.addEventListener('change', filter);
+                
+                if (resourceParam) {
+                    cnav.querySelectorAll('.contentnav__item').forEach(function (item) {
+                        if (item.querySelector('*[data-value="' + resourceParam + '"]')) {
+                            item.classList.remove('is-hidden');
+                            item.click();
+                        } else {
+                            item.classList.add('is-hidden');
+                        }
+                    });
+                    cnav.querySelector('input[name=search]').value = resourceParam;
+                } else if (searchParam) {
+                    cnav.querySelector('input[name=search]').value = searchParam;
+                    filter(new Event('fake'));
+                }
+            }
+        }
+    });
+    rava.bind(".contentnav .contentnav__item", {
+        events: {
+            click: function () {
+                this.closest('.contentnav').querySelectorAll('.contentnav__item.is-selected').forEach(function (tr) {
+                    tr.classList.remove('is-selected');
+                });
+                this.classList.add('is-selected');
+                document.querySelector('.actions-target').innerHTML = this.querySelector('.cell-actions').innerHTML;
+            },
+            dblclick: function () {
+                if(this.querySelector('.item-link')){
+                    window.location = this.querySelector('.item-link').href;
+                }
+            }
+        }
+    });
 }(window.rava = window.rava || {}));
\ No newline at end of file
diff --git a/ui/src/main/frontend/js/cms.table.js b/ui/src/main/frontend/js/cms.table.js
deleted file mode 100644
index 45bc48f..0000000
--- a/ui/src/main/frontend/js/cms.table.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/* eslint-env browser, es6 */
-(function (rava) {
-    'use strict';
-    
-    var urlParams = new URLSearchParams(window.location.search),
-        resourceParam = urlParams.get('resource'),
-        searchParam = urlParams.get('search');
-    
-    rava.bind(".table tbody tr", {
-        events: {
-            click: function () {
-                this.closest('.table').querySelectorAll('tr.is-selected').forEach(function (tr) {
-                    tr.classList.remove('is-selected');
-                });
-                this.classList.add('is-selected');
-                document.querySelector('.actions-target').innerHTML = this.querySelector('.cell-actions').innerHTML;
-            },
-            dblclick: function () {
-                if(this.querySelector('.item-link')){
-                    window.location = this.querySelector('.item-link').href;
-                }
-            }
-        }
-    });
-    
-    rava.bind(".table", {
-        callbacks : {
-            created: function () {
-                var table = this;
-                if (table.closest('.table__wrapper')) {
-                    var search = table.closest('.table__wrapper').querySelector('input[name=search]'),
-                        filter = function (event) {
-                            event.stopPropagation();
-                            event.preventDefault();
-                            var value = this.value.toLowerCase();
-                            table.querySelectorAll('tbody tr').forEach(function (row) {
-                                if (row.innerText.toLowerCase().indexOf(value) === -1 && !row.querySelector('td[data-value="' + resourceParam + '"]')) {
-                                    row.classList.add('is-hidden');
-                                } else {
-                                    row.classList.remove('is-hidden');
-                                }
-                            });
-                        };
-                    search.addEventListener('keyup', filter);
-                    search.addEventListener('change', filter);
-                    
-                    if (resourceParam) {
-                        table.querySelectorAll('tbody tr').forEach(function (row) {
-                            if (row.querySelector('td[data-value="' + resourceParam + '"]')) {
-                                row.classList.remove('is-hidden');
-                                row.click();
-                            } else {
-                                row.classList.add('is-hidden');
-                            }
-                        });
-                        table.closest('.table__wrapper').querySelector('input[name=search]').value = resourceParam;
-                    } else if (searchParam) {
-                        table.closest('.table__wrapper').querySelector('input[name=search]').value = searchParam;
-                        filter(new Event('fake'));
-                    }
-                }
-            }
-        }
-    });
-
-}(window.rava = window.rava || {}));
\ No newline at end of file
diff --git a/ui/src/main/frontend/scss/cms.scss b/ui/src/main/frontend/scss/cms.scss
index 94145a5..cae78d7 100644
--- a/ui/src/main/frontend/scss/cms.scss
+++ b/ui/src/main/frontend/scss/cms.scss
@@ -63,8 +63,25 @@ h5 {
     font-size: 120%;
 }
 
+.card.is-selected {
+    border: 1px solid $violet;
+}
+
+.card-footer-item {
+    word-break: break-all;
+}
+
+.container {
+    height: 100vh;
+    overflow: hidden;
+    @media screen and (max-width: $tablet - 1px) {
+        height: 100%;
+        overflow: visible;
+    }
+}
+
 .has-padding-1 {
-	padding: .5em;
+    padding: .5em;
 }
 
 .editor-page {
@@ -91,6 +108,10 @@ h5 {
     cursor: not-allowed;
 }
 
+.is-linked {
+    cursor: pointer;
+}
+
 .is-draggable {
     position: relative;
 }
@@ -146,21 +167,43 @@ h5 {
 }
 
 .rte-form {
-	margin: .5em 0;
+    margin: .5em 0;
 }
 
 .rte-form .buttons {
-	margin: .5em 0;
+    margin: .5em 0;
 }
 
 .rte-toolbar .jam:before {
-	font-size: x-large;
+    font-size: x-large;
 }
 
 .rte-toolbar .level {
     margin-bottom: .5em;
 }
 
+.scroll-container {
+    overflow-y: auto;
+    height: calc(100vh - 190px);
+    overflow-x: hidden;
+    padding: 2px;
+    @media screen and (max-width: $tablet - 1px) {
+        height: 100%;
+        overflow: visible;
+        padding: 0px;
+    }
+}
+
+.scroll-container .scroll-container {
+    height: auto;
+    overflow: visible;
+    padding: 0px;
+}
+
+.scroll-container > .is-ancestor {
+    flex-wrap: wrap;
+}
+
 #search-results .box {
     overflow: hidden;
 }
@@ -205,7 +248,7 @@ table thead .sorting_desc:after {
 }
 
 .wysihtml-sandbox {
-	width: 100% !important;
-	max-height: 600px !important;
+    width: 100% !important;
+    max-height: 600px !important;
     min-height: 300px !important;
 }
diff --git a/ui/src/main/resources/SLING-INF/nodetypes/nodetypes.cnd b/ui/src/main/resources/SLING-INF/nodetypes/nodetypes.cnd
index 0f030c5..04a8db3 100644
--- a/ui/src/main/resources/SLING-INF/nodetypes/nodetypes.cnd
+++ b/ui/src/main/resources/SLING-INF/nodetypes/nodetypes.cnd
@@ -48,6 +48,7 @@
     - * (undefined) copy
     - * (undefined) copy multiple
     + metadata (nt:unstructured) = nt:unstructured copy primary
+    + renditions (nt:unstructured) = nt:unstructured copy primary
 
 [sling:Page] > nt:hierarchyNode
     orderable
diff --git a/ui/src/main/resources/jcr_root/conf/global.json b/ui/src/main/resources/jcr_root/conf/global.json
index 6639ba5..97413db 100644
--- a/ui/src/main/resources/jcr_root/conf/global.json
+++ b/ui/src/main/resources/jcr_root/conf/global.json
@@ -12,6 +12,7 @@
         },
         "editors": {
             "jcr:primaryType": "sling:Config",
+            "jcr:title": "File Editors",
             "sling:resourceType": "sling-cms/components/caconfig/fileeditor",
             "default": {
                 "jcr:primaryType": "nt:unstructured",
@@ -76,6 +77,68 @@
                     }
                 }
             }
+        },
+        "transformations": {
+            "jcr:primaryType": "sling:Config",
+            "jcr:title": "Transformations",
+            "sling:resourceType": "sling-cms/components/caconfig/transformations",
+            "sling-cms-thumbnail": {
+                "jcr:primaryType": "nt:unstructured",
+                "name": "sling-cms-thumbnail",
+                "sling:resourceType": "sling-cms/components/caconfig/transformation",
+                "handlers": {
+                    "jcr:primaryType": "nt:unstructured",
+                    "size": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "height": "600",
+                        "width": "480",
+                        "sling:resourceType": "sling-cms/components/caconfig/transformationhandlers/size"
+                    },
+                    "crop": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "position": "CENTER",
+                        "sling:resourceType": "sling-cms/components/caconfig/transformationhandlers/crop"
+                    }
+                }
+            },
+            "sling-cms-thumbnail128": {
+                "jcr:primaryType": "nt:unstructured",
+                "name": "sling-cms-thumbnail128",
+                "sling:resourceType": "sling-cms/components/caconfig/transformation",
+                "handlers": {
+                    "jcr:primaryType": "nt:unstructured",
+                    "size": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "height": "128",
+                        "width": "128",
+                        "sling:resourceType": "sling-cms/components/caconfig/transformationhandlers/size"
+                    },
+                    "crop": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "position": "CENTER",
+                        "sling:resourceType": "sling-cms/components/caconfig/transformationhandlers/crop"
+                    }
+                }
+            },
+            "sling-cms-thumbnail32": {
+                "jcr:primaryType": "nt:unstructured",
+                "name": "sling-cms-thumbnail32",
+                "sling:resourceType": "sling-cms/components/caconfig/transformation",
+                "handlers": {
+                    "jcr:primaryType": "nt:unstructured",
+                    "size": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "height": "32",
+                        "width": "32",
+                        "sling:resourceType": "sling-cms/components/caconfig/transformationhandlers/size"
+                    },
+                    "crop": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "position": "CENTER",
+                        "sling:resourceType": "sling-cms/components/caconfig/transformationhandlers/crop"
+                    }
+                }
+            }
         }
     },
     "site": {
@@ -96,7 +159,7 @@
             ]
         },
         "policies": {
-            "jcr:primaryType": "sling:Config", 
+            "jcr:primaryType": "sling:Config",
             "jcr:title": "Default Policy",
             "sling:resourceType": "sling-cms/components/caconfig/policies",
             "policy": {
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp
index 69e44fa..f130dc8 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp
@@ -18,5 +18,7 @@
  */ --%>
 <%@include file="/libs/sling-cms/global.jsp"%>
 <c:if test="${slingRequest.requestPathInfo.suffixResource != null}">
-    <sling:include resource="${slingRequest.requestPathInfo.suffixResource}" />
+    <div class="scroll-container">
+        <sling:include resource="${slingRequest.requestPathInfo.suffixResource}" />
+    </div>
 </c:if>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/config/config.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/config/config.jsp
index 2705df3..0291f24 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/config/config.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/config/config.jsp
@@ -19,6 +19,9 @@
 <%@include file="/libs/sling-cms/global.jsp"%>
 <br/>
 <h3><sling:encode value="${properties['jcr:title']}" mode="HTML" /> (${resource.path})</h3>
+<c:if test="${sling:getRelativeResource(resource, 'thumbnail') != null}">
+    <img src="${resource.path}/thumbnail.transform/sling-cms-thumbnail128.png" alt="Thumbnail" class="image is-128x128" />
+</c:if>
 <br/>
 <div>
     <h4>Allowed Paths</h4>
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/config/edit.json b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/config/edit.json
index cba3207..e01bec3 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/config/edit.json
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/config/edit.json
@@ -12,6 +12,19 @@
             "name": "jcr:title",
             "required": true
         },
+        "thumbnail": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Thumbnail",
+            "name": "thumbnail",
+            "type": "file"
+        },
+        "thumbnailTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "thumbnail@TypeHint",
+            "value": "sling:File"
+        },
         "allowedPaths": {
             "jcr:primaryType": "nt:unstructured",
             "sling:resourceType": "sling-cms/components/editor/fields/repeating",
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/template.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/template.jsp
index f90e8b8..2fdf949 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/template.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/template.jsp
@@ -17,8 +17,10 @@
  * under the License.
  */ --%>
 <%@include file="/libs/sling-cms/global.jsp"%>
- <c:set var="cmsEditEnabled" value="true" scope="request" />
-<sling:call script="/libs/sling-cms/components/editor/scripts/init.jsp" />
-<sling:include path="${slingRequest.requestPathInfo.suffix}" resourceType="sling-cms/components/caconfig/template/config" />
-<sling:call script="/libs/sling-cms/components/editor/scripts/finalize.jsp" />
-<c:set var="cmsEditEnabled" value="false" scope="request" />
\ No newline at end of file
+<div class="scroll-container">
+    <c:set var="cmsEditEnabled" value="true" scope="request" />
+    <sling:call script="/libs/sling-cms/components/editor/scripts/init.jsp" />
+    <sling:include path="${slingRequest.requestPathInfo.suffix}" resourceType="sling-cms/components/caconfig/template/config" />
+    <sling:call script="/libs/sling-cms/components/editor/scripts/finalize.jsp" />
+    <c:set var="cmsEditEnabled" value="false" scope="request" />
+</div>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformation/config.json b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformation/config.json
new file mode 100644
index 0000000..ef57689
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformation/config.json
@@ -0,0 +1,4 @@
+{
+	"jcr:primaryType": "sling:Component",
+    "jcr:title": "Sling CMS - Transformation Configuration"
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/template.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformation/config/config.jsp
similarity index 62%
copy from ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/template.jsp
copy to ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformation/config/config.jsp
index f90e8b8..117d4a2 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/template.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformation/config/config.jsp
@@ -17,8 +17,20 @@
  * under the License.
  */ --%>
 <%@include file="/libs/sling-cms/global.jsp"%>
- <c:set var="cmsEditEnabled" value="true" scope="request" />
-<sling:call script="/libs/sling-cms/components/editor/scripts/init.jsp" />
-<sling:include path="${slingRequest.requestPathInfo.suffix}" resourceType="sling-cms/components/caconfig/template/config" />
-<sling:call script="/libs/sling-cms/components/editor/scripts/finalize.jsp" />
-<c:set var="cmsEditEnabled" value="false" scope="request" />
\ No newline at end of file
+<br/>
+<div>
+    <dl>
+        <dt>Name</dt>
+        <dd><sling:encode value="${properties.name}" mode="HTML" />
+    </dl>
+</div>
+<hr/>
+<div>
+    <h4>
+        Transformation Handlers
+    </h4>
+    <c:set var="oldAvailableTypes" value="${availableTypes}" />
+    <c:set var="availableTypes" value="SlingCMS-TransformationHandler" scope="request" />
+    <sling:include path="handlers" resourceType="sling-cms/components/general/reloadcontainer" />
+    <c:set var="availableTypes" value="${oldAvailableTypes}" scope="request" />
+</div>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformation/config/edit.json b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformation/config/edit.json
new file mode 100644
index 0000000..6c8dd08
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformation/config/edit.json
@@ -0,0 +1,16 @@
+ {
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save Transformation",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "name": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Name",
+            "name": "name",
+            "required": true
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/template.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformation/transformation.jsp
similarity index 65%
copy from ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/template.jsp
copy to ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformation/transformation.jsp
index f90e8b8..2534257 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/template.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformation/transformation.jsp
@@ -17,8 +17,10 @@
  * under the License.
  */ --%>
 <%@include file="/libs/sling-cms/global.jsp"%>
- <c:set var="cmsEditEnabled" value="true" scope="request" />
-<sling:call script="/libs/sling-cms/components/editor/scripts/init.jsp" />
-<sling:include path="${slingRequest.requestPathInfo.suffix}" resourceType="sling-cms/components/caconfig/template/config" />
-<sling:call script="/libs/sling-cms/components/editor/scripts/finalize.jsp" />
-<c:set var="cmsEditEnabled" value="false" scope="request" />
\ No newline at end of file
+<div class="scroll-container">
+    <c:set var="cmsEditEnabled" value="true" scope="request" />
+    <sling:call script="/libs/sling-cms/components/editor/scripts/init.jsp" />
+    <sling:include path="${slingRequest.requestPathInfo.suffix}" resourceType="sling-cms/components/caconfig/transformation/config" />
+    <sling:call script="/libs/sling-cms/components/editor/scripts/finalize.jsp" />
+    <c:set var="cmsEditEnabled" value="false" scope="request" />
+</div>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/crop.json b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/crop.json
new file mode 100644
index 0000000..6b9670f
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/crop.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType": "sling:Component",
+    "jcr:title": "Sling CMS - Transformation Crop Handler",
+    "componentType": "SlingCMS-TransformationHandler"
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/crop/crop.jsp
similarity index 80%
copy from ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp
copy to ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/crop/crop.jsp
index 69e44fa..f5d6a7d 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/crop/crop.jsp
@@ -16,7 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */ --%>
-<%@include file="/libs/sling-cms/global.jsp"%>
-<c:if test="${slingRequest.requestPathInfo.suffixResource != null}">
-    <sling:include resource="${slingRequest.requestPathInfo.suffixResource}" />
-</c:if>
\ No newline at end of file
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<dl>
+    <dt>Position</dt>
+    <dd>${properties.position}</dd>
+</dl>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/crop/edit.json b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/crop/edit.json
new file mode 100644
index 0000000..4aa1dbb
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/crop/edit.json
@@ -0,0 +1,53 @@
+ {
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "position": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/select",
+            "label": "Position",
+            "name": "position",
+            "options": {
+                "CENTER": {
+                    "label": "Center",
+                    "value": "CENTER"
+                },
+                "BOTTOM_CENTER": {
+                    "label": "Bottom Center",
+                    "value": "BOTTOM_CENTER"
+                },
+                "BOTTOM_LEFT": {
+                    "label": "Bottom Left",
+                    "value": "BOTTOM_LEFT"
+                },
+                "BOTTOM_RIGHT": {
+                    "label": "Bottom Right",
+                    "value": "BOTTOM_RIGHT"
+                },
+                "CENTER_LEFT": {
+                    "label": "Center Left",
+                    "value": "CENTER_LEFT"
+                },
+                "CENTER_RIGHT": {
+                    "label": "Center Right",
+                    "value": "CENTER_RIGHT"
+                },
+                "TOP_LEFT": {
+                    "label": "Top Left",
+                    "value": "TOP_LEFT"
+                },
+                "TOP_CENTER": {
+                    "label": "Top Center",
+                    "value": "TOP_CENTER"
+                },
+                "TOP_RIGHT": {
+                    "label": "Top Right",
+                    "value": "TOP_RIGHT"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/size.json b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/size.json
new file mode 100644
index 0000000..55709a9
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/size.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType": "sling:Component",
+    "jcr:title": "Sling CMS - Transformation Size Handler",
+    "componentType": "SlingCMS-TransformationHandler"
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/size/edit.json b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/size/edit.json
new file mode 100644
index 0000000..4469a65
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/size/edit.json
@@ -0,0 +1,23 @@
+ {
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "width": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Width",
+            "name": "width",
+            "type": "number"
+        },
+        "height": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Height",
+            "name": "height",
+            "type": "number"
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/size/size.jsp
similarity index 80%
copy from ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp
copy to ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/size/size.jsp
index 69e44fa..8c7ad92 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformationhandlers/size/size.jsp
@@ -16,7 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */ --%>
-<%@include file="/libs/sling-cms/global.jsp"%>
-<c:if test="${slingRequest.requestPathInfo.suffixResource != null}">
-    <sling:include resource="${slingRequest.requestPathInfo.suffixResource}" />
-</c:if>
\ No newline at end of file
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<dl>
+    <dt>Width</dt>
+    <dd>${properties.width}</dd>
+    <dt>Height</dt>
+    <dd>${properties.height}</dd>
+</dl>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformations.json b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformations.json
new file mode 100644
index 0000000..3ea8caa
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformations.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType": "sling:Component",
+    "jcr:title": "Sling CMS - Transformations Configuration",
+    "componentType": "SlingCMS-Config"
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformations/transformations.jsp
similarity index 76%
copy from ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp
copy to ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformations/transformations.jsp
index 69e44fa..38f596d 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/transformations/transformations.jsp
@@ -16,7 +16,5 @@
  * specific language governing permissions and limitations
  * under the License.
  */ --%>
-<%@include file="/libs/sling-cms/global.jsp"%>
-<c:if test="${slingRequest.requestPathInfo.suffixResource != null}">
-    <sling:include resource="${slingRequest.requestPathInfo.suffixResource}" />
-</c:if>
\ No newline at end of file
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<sling:include path="/mnt/overlay/sling-cms/content/transformations/editor" resourceType="sling-cms/components/general/container" replaceSuffix="${slingRequest.requestPathInfo.suffixResource.parent.path}" />
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentactions/contentactions.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentactions/contentactions.jsp
index 5880c78..e057216 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentactions/contentactions.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentactions/contentactions.jsp
@@ -18,17 +18,56 @@
  */ --%>
 <%@include file="/libs/sling-cms/global.jsp"%>
 <nav class="level">
-	<div class="level-left">
-		<div class="level-item">
-			<div class="buttons has-addons">
-				<c:forEach var="action" items="${sling:listChildren(sling:getRelativeResource(resource,'actions'))}" varStatus="status">
-					<a class="button Fetch-Modal" data-title="Add ${action.valueMap.label}" data-path=".Main-Content form" href="${action.valueMap.prefix}${slingRequest.requestPathInfo.suffix}">+ ${action.valueMap.label}</a>
-				</c:forEach>
-			</div>
-		</div>
-		<div class="level-item">
-			<div class="buttons has-addons actions-target">
-			</div>
-		</div>
-	</div>
+    <div class="level-left">
+        <div class="level-item">
+            <div class="buttons has-addons">
+                <c:forEach var="action" items="${sling:listChildren(sling:getRelativeResource(resource,'actions'))}" varStatus="status">
+                    <a class="button Fetch-Modal" data-title="Add ${action.valueMap.label}" data-path=".Main-Content form" href="${action.valueMap.prefix}${slingRequest.requestPathInfo.suffix}">+ ${action.valueMap.label}</a>
+                </c:forEach>
+            </div>
+        </div>
+        <div class="level-item">
+            <div class="buttons has-addons actions-target">
+            </div>
+        </div>
+    </div>
+    <c:if test="${properties.includeSwitcher}">
+        <div class="level-right">
+            <div class="level-item">
+                <div class="field">
+                    <div class="control has-icons-left">
+                        <sling:adaptTo adaptable="${resourceResolver}" adaptTo="org.apache.sling.cms.AuthorizableWrapper" var="auth" />
+                        <sling:getResource path="${auth.authorizable.path}/profile" var="profile" />
+                        <c:set var="pagePath" value="${sling:adaptTo(resource,'org.apache.sling.cms.PageManager').page.path}" />
+                        <form method="get" action="" class="layout-switch">
+                            <div class="select">
+                                <select>
+                                    <c:choose>
+                                        <c:when test="${slingRequest.requestPathInfo.selectorString == 'table' || (profile.valueMap.defaultLayout == 'table' && slingRequest.requestPathInfo.selectorString != 'grid')}">
+                                            <option value="/cms${fn:substring(pagePath,30,fn:length(pagePath))}.grid.html${slingRequest.requestPathInfo.suffix}">Grid</option>
+                                            <option selected value="/cms${fn:substring(pagePath,30,fn:length(pagePath))}.table.html${slingRequest.requestPathInfo.suffix}">Table</option>
+                                        </c:when>
+                                        <c:otherwise>
+                                            <option selected value="/cms${fn:substring(pagePath,30,fn:length(pagePath))}.grid.html${slingRequest.requestPathInfo.suffix}">Grid</option>
+                                            <option value="/cms${fn:substring(pagePath,30,fn:length(pagePath))}.table.html${slingRequest.requestPathInfo.suffix}">Table</option>
+                                        </c:otherwise>
+                                    </c:choose>
+                                </select>
+                            </div>
+                            <div class="icon is-small is-left">
+                                <c:choose>
+                                    <c:when test="${slingRequest.requestPathInfo.selectorString == 'table' || (profile.valueMap.defaultLayout == 'table' && slingRequest.requestPathInfo.selectorString != 'grid')}">
+                                        <i class="jam jam-unordered-list"></i>
+                                    </c:when>
+                                    <c:otherwise>
+                                        <i class="jam jam-grid"></i>
+                                    </c:otherwise>
+                                </c:choose>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </c:if>
 </nav>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentbreadcrumb/contentbreadcrumb.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentbreadcrumb/contentbreadcrumb.jsp
index 694fd5e..1fc2a82 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentbreadcrumb/contentbreadcrumb.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentbreadcrumb/contentbreadcrumb.jsp
@@ -19,18 +19,18 @@
  <%@include file="/libs/sling-cms/global.jsp"%>
 <sling:adaptTo var="breadcrumb" adaptable="${slingRequest}" adaptTo="org.apache.sling.cms.core.models.ContentBreadcrumb" />
 <nav class="breadcrumb" aria-label="breadcrumbs">
-<ul>
-    <c:forEach var="parent" items="${breadcrumb.parents}">
-        <li>
-            <a href="${parent.left}">
-                <sling:encode value="${parent.right}" mode="HTML" />
+    <ul>
+        <c:forEach var="parent" items="${breadcrumb.parents}">
+            <li>
+                <a href="${parent.left}">
+                    <sling:encode value="${parent.right}" mode="HTML" />
+                </a>
+            </li>
+        </c:forEach>
+        <li class="is-active">
+            <a href="#">
+                <sling:encode value="${breadcrumb.currentItem}" mode="HTML" />
             </a>
         </li>
-    </c:forEach>
-    <li class="is-active">
-        <a href="#">
-            <sling:encode value="${breadcrumb.currentItem}" mode="HTML" />
-        </a>
-    </li>
-</ul>
+    </ul>
 </nav>
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentgrid/contentgrid.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentgrid/contentgrid.jsp
new file mode 100644
index 0000000..0bb2c75
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentgrid/contentgrid.jsp
@@ -0,0 +1,114 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<div class="reload-container scroll-container contentnav" data-path="${resource.path}.grid.html${slingRequest.requestPathInfo.suffix}">
+    <form method="get" class="table__filter">
+        <p class="control has-icons-left">
+            <label class="is-vhidden" for="search-term">Search</label>
+            <input class="input is-small" type="text" name="search" id="search-term">
+            <span class="icon is-small is-left">
+                <i class="jam jam-search" aria-hidden="true"></i>
+            </span>
+        </p>
+    </form>
+    <div class="tile is-ancestor">
+        <c:forEach var="child" items="${sling:listChildren(slingRequest.requestPathInfo.suffixResource)}" varStatus="status">
+            <c:set var="showCard" value="${false}" />
+            <c:forEach var="type" items="${sling:listChildren(sling:getRelativeResource(resource,'types'))}">
+                <c:if test="${child.valueMap['jcr:primaryType'] == type.name}">
+                    <c:set var="showCard" value="${true}" />
+                </c:if>
+            </c:forEach>
+            <c:if test="${showCard}">
+                <div class="tile is-parent is-3 contentnav__item">
+                        <div class="tile is-child">
+                            <div class="card is-linked " data-value="${child.path}">
+                                <div class="card-image">
+                                    <figure class="image is-5by4">
+                                        <c:choose>
+                                            <c:when test="${child.resourceType == 'sling:File' || child.resourceType == 'nt:file'}">
+                                                <img src="${child.path}.transform/sling-cms-thumbnail.png" alt="${child.name}">
+                                            </c:when>
+                                            <c:when test="${child.resourceType == 'sling:Site'}">
+                                                <img src="/static/sling-cms/thumbnails/site.png.transform/sling-cms-thumbnail.png" alt="${sling:encode(child.name, 'HTML_ATTR')}">
+                                            </c:when>
+                                            <c:when test="${child.resourceType == 'sling:OrderedFolder' || child.resourceType == 'sling:Folder' || child.resourceType == 'nt:folder'}">
+                                                <img src="/static/sling-cms/thumbnails/folder.png.transform/sling-cms-thumbnail.png" alt="${sling:encode(child.name, 'HTML_ATTR')}">
+                                            </c:when>
+                                            <c:when test="${child.resourceType == 'sling:Page'}">
+                                                <c:set var="templateThumbnail" value="${child.valueMap['jcr:content/sling:template']}/thumbnail"/>
+                                                <c:choose>
+                                                    <c:when test="${sling:getResource(resourceResolver, templateThumbnail) != null}">
+                                                        <img src="${templateThumbnail}.transform/sling-cms-thumbnail.png" alt="${sling:encode(child.name, 'HTML_ATTR')}">
+                                                    </c:when>
+                                                    <c:otherwise>
+                                                        <img src="/static/sling-cms/thumbnails/page.png.transform/sling-cms-thumbnail.png" alt="${child.name}">
+                                                    </c:otherwise>
+                                                </c:choose>
+                                            </c:when>
+                                            <c:otherwise>
+                                                <img src="/static/sling-cms/thumbnails/file.png.transform/sling-cms-thumbnail.png" alt="${child.name}">
+                                            </c:otherwise>
+                                        </c:choose>
+                                    </figure>
+                                    <div class="is-vhidden cell-actions">
+                                        <sling:getResource base="${resource}" path="types/${child.valueMap['jcr:primaryType']}/columns/actions" var="colConfig" />
+                                
+                                        <c:forEach var="actionConfig" items="${sling:listChildren(colConfig)}">
+                                            <c:choose>
+                                                <c:when test="${actionConfig.valueMap.modal}">
+                                                    <a class="button Fetch-Modal" data-title="${sling:encode(actionConfig.valueMap.title,'HTML_ATTR')}" data-path="${actionConfig.valueMap.ajaxPath != null ? actionConfig.valueMap.ajaxPath : '.Main-Content form'}" href="${actionConfig.valueMap.prefix}${child.path}" title="${sling:encode(actionConfig.valueMap.title,'HTML_ATTR')}">
+                                                        <span class="jam jam-${actionConfig.valueMap.icon}">
+                                                            <span class="is-vhidden">
+                                                                ${sling:encode(actionConfig.valueMap.title,'HTML')}
+                                                            </span>
+                                                        </span>
+                                                    </a>
+                                                </c:when>
+                                                <c:otherwise>
+                                                    <a class="button" ${actionConfig.valueMap.new != false ? 'target="_blank"' : ''} href="${actionConfig.valueMap.prefix}${child.path}" title="${sling:encode(actionConfig.valueMap.title,'HTML_ATTR')}">
+                                                        <span class="jam jam-${actionConfig.valueMap.icon}">
+                                                            <span class="is-vhidden">
+                                                                ${sling:encode(actionConfig.valueMap.title,'HTML')}
+                                                            </span>
+                                                        </span>
+                                                    </a>
+                                                </c:otherwise>
+                                            </c:choose>
+                                        </c:forEach>
+                                    </div>
+                                </div>
+                                <footer class="card-footer">
+                                    <sling:getResource base="${resource}" path="types/${child.valueMap['jcr:primaryType']}/columns/name" var="nameConfig" />
+                                    <c:choose>
+                                        <c:when test="${child.resourceType == 'sling:Site' || child.resourceType == 'sling:OrderedFolder' || child.resourceType == 'sling:Folder' || child.resourceType == 'nt:folder' || child.resourceType == 'sling:Page'}">
+                                            <a href="${nameConfig.valueMap.prefix}${child.path}" class="card-footer-item item-link">${child.name}</a>
+                                        </c:when>
+                                        <c:otherwise>
+                                            <span class="card-footer-item">${child.name}</span>
+                                        </c:otherwise>
+                                    </c:choose>
+                                </footer>
+                            </div>
+                        </div>
+                    </div>
+                </c:if>
+        </c:forEach>
+    </div>
+</div>
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/template.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentlayout/contentlayout.jsp
similarity index 57%
copy from ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/template.jsp
copy to ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentlayout/contentlayout.jsp
index f90e8b8..0dd476c 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/template/template.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentlayout/contentlayout.jsp
@@ -17,8 +17,13 @@
  * under the License.
  */ --%>
 <%@include file="/libs/sling-cms/global.jsp"%>
- <c:set var="cmsEditEnabled" value="true" scope="request" />
-<sling:call script="/libs/sling-cms/components/editor/scripts/init.jsp" />
-<sling:include path="${slingRequest.requestPathInfo.suffix}" resourceType="sling-cms/components/caconfig/template/config" />
-<sling:call script="/libs/sling-cms/components/editor/scripts/finalize.jsp" />
-<c:set var="cmsEditEnabled" value="false" scope="request" />
\ No newline at end of file
+<sling:adaptTo adaptable="${resourceResolver}" adaptTo="org.apache.sling.cms.AuthorizableWrapper" var="auth" />
+<sling:getResource path="${auth.authorizable.path}/profile" var="profile" />
+<c:choose>
+    <c:when test="${slingRequest.requestPathInfo.selectorString == 'table' || (profile.valueMap.defaultLayout == 'table' && slingRequest.requestPathInfo.selectorString != 'grid')}">
+        <sling:include path="${resource.path}" resourceType="sling-cms/components/cms/contenttable" />
+    </c:when>
+    <c:otherwise>
+        <sling:include path="${resource.path}" resourceType="sling-cms/components/cms/contentgrid" />
+    </c:otherwise>
+</c:choose>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contenttable/contenttable.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contenttable/contenttable.jsp
index bb085e5..cde6ae5 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contenttable/contenttable.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contenttable/contenttable.jsp
@@ -17,7 +17,7 @@
  * under the License.
  */ --%>
  <%@include file="/libs/sling-cms/global.jsp"%>
- <div class="reload-container table__wrapper" data-path="${resource.path}.html${slingRequest.requestPathInfo.suffix}">
+ <div class="reload-container table__wrapper scroll-container contentnav" data-path="${resource.path}.html${slingRequest.requestPathInfo.suffix}">
     <form method="get" class="table__filter">
         <p class="control has-icons-left">
             <label class="is-vhidden" for="search-term">Search</label>
@@ -46,7 +46,7 @@
             <c:forEach var="child" items="${sling:listChildren(sling:getResource(resourceResolver, parentPath))}" varStatus="status">
                 <sling:getResource var="typeConfig" base="${resource}" path="types/${child.valueMap['jcr:primaryType']}" />
                 <c:if test="${typeConfig != null && !fn:contains(child.name,':')}">
-                    <tr class="sortable__row" data-resource="${child.path}" data-type="${typeConfig.path}">
+                    <tr class="contentnav__item sortable__row" data-resource="${child.path}" data-type="${typeConfig.path}">
                         <td class="Cell-Static" title="# ${status.index + 1}" data-sort-value="<fmt:formatNumber pattern="0000" value="${count}" />">
                             ${count}
                         </td>
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/i18ncontainer/i18ncontainer.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/i18ncontainer/i18ncontainer.jsp
new file mode 100644
index 0000000..70829c1
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/i18ncontainer/i18ncontainer.jsp
@@ -0,0 +1,89 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<div class="scroll-container">
+    <sling:include path="${resource.path}" resourceType="sling-cms/components/general/container" />
+    <c:forEach var="language" items="${sling:listChildren(slingRequest.requestPathInfo.suffixResource)}">
+        <c:if test="${firstChild == null && not empty language.valueMap['jcr:language']}">
+            <c:set var="firstChild" value="${language}" />
+        </c:if>
+    </c:forEach>
+    <nav class="level">
+        <div class="level-left">
+            <div class="level-item">
+                <a class="Button Fetch-Modal" data-title="Add Entry" data-path=".Main-Content form" href="/cms/i18n/entry/create.html${firstChild.path}">+ Entry</a>
+            </div>
+        </div>
+    </nav>
+    <form method="post" action="${slingRequest.requestPathInfo.suffix}" enctype="multipart/form-data" class="Form-Ajax" data-add-date="false">
+        <fieldset class="form-wrapper field">
+            <input type="hidden" name="_charset_" value="utf-8" />
+            <table class="table is-fullwidth is-striped">
+                <thead>
+                    <tr>
+                        <th class="Column-key">
+                            Key
+                        </th>
+                        <c:forEach var="language" items="${sling:listChildren(slingRequest.requestPathInfo.suffixResource)}">
+                            <c:if test="${not empty language.valueMap['jcr:language']}">
+                                <th class="Column-${language.valueMap['jcr:language']}">
+                                    <sling:adaptTo adaptable="${language}" adaptTo="org.apache.sling.cms.core.models.LocaleResource" var="localeResource" />
+                                    <sling:encode value="${localeResource.locale.displayLanguage}" mode="HTML" /> <sling:encode value="${localeResource.locale.displayCountry}" mode="HTML" />
+                                    <br/>
+                                    <small>(<sling:encode value="${language.valueMap['jcr:language']}" mode="HTML" />)</small>
+                                </th>
+                            </c:if>
+                        </c:forEach>
+                    </tr>
+                </thead>
+                <tbody>
+                    <sling:adaptTo var="helper" adaptable="${slingRequest.requestPathInfo.suffixResource}" adaptTo="org.apache.sling.cms.core.models.i18nHelper" />
+                    <c:forEach var="key" items="${helper.keys}">
+                        <tr>
+                            <td>
+                                <sling:encode value="${key}" mode="HTML" />
+                            </td>
+                            <c:forEach var="language" items="${sling:listChildren(slingRequest.requestPathInfo.suffixResource)}">
+                                <c:if test="${not empty language.valueMap['jcr:language']}">
+                                    <td>
+                                        <c:set var="keyfound" value="false" />
+                                        <c:forEach var="entry" items="${sling:listChildren(language)}">
+                                            <c:if test="${entry.valueMap['sling:key'] == key}">
+                                                <c:set var="keyfound" value="true" />
+                                                <input name="${language.name}/${entry.name}/sling:message" class="input" type="text" value="${sling:encode(entry.valueMap['sling:message'],'HTML_ATTR')}" />
+                                                <input name="${language.name}/${entry.name}/sling:key" type="hidden" value="${key}" />
+                                            </c:if>
+                                        </c:forEach>
+                                        <c:if test="${keyfound == 'false'}">
+                                            <c:set var="rand" value="${helper.random}" />
+                                            <input name="${language.name}/entry_${rand}/sling:message" class="input" type="text" value="" />
+                                            <input name="${language.name}/entry_${rand}/sling:key" type="hidden" value="${key}" />
+                                            <input name="${language.name}/entry_${rand}/jcr:primaryType" type="hidden" value="sling:MessageEntry" />
+                                        </c:if>
+                                    </td>
+                                </c:if>
+                            </c:forEach>
+                        </tr>
+                    </c:forEach>
+                </tbody>
+            </table>
+            <button class="button is-primary">Save i18n Dictionary</button>
+        </fieldset>
+    </form>
+</div>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/i18ntable/i18ntable.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/i18ntable/i18ntable.jsp
deleted file mode 100644
index 467bef1..0000000
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/i18ntable/i18ntable.jsp
+++ /dev/null
@@ -1,87 +0,0 @@
-<%-- /*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */ --%>
- <%@include file="/libs/sling-cms/global.jsp"%>
-
-<c:forEach var="language" items="${sling:listChildren(slingRequest.requestPathInfo.suffixResource)}">
-    <c:if test="${firstChild == null && not empty language.valueMap['jcr:language']}">
-        <c:set var="firstChild" value="${language}" />
-    </c:if>
-</c:forEach>
-<nav class="level">
-    <div class="level-left">
-        <div class="level-item">
-            <a class="Button Fetch-Modal" data-title="Add Entry" data-path=".Main-Content form" href="/cms/i18n/entry/create.html${firstChild.path}">+ Entry</a>
-        </div>
-    </div>
-</nav>
-<form method="post" action="${slingRequest.requestPathInfo.suffix}" enctype="multipart/form-data" class="Form-Ajax" data-add-date="false">
-    <fieldset class="form-wrapper field">
-        <input type="hidden" name="_charset_" value="utf-8" />
-        <table class="table is-fullwidth is-striped">
-            <thead>
-                <tr>
-                    <th class="Column-key">
-                        Key
-                    </th>
-                    <c:forEach var="language" items="${sling:listChildren(slingRequest.requestPathInfo.suffixResource)}">
-                        <c:if test="${not empty language.valueMap['jcr:language']}">
-                            <th class="Column-${language.valueMap['jcr:language']}">
-                                <sling:adaptTo adaptable="${language}" adaptTo="org.apache.sling.cms.core.models.LocaleResource" var="localeResource" />
-                                <sling:encode value="${localeResource.locale.displayLanguage}" mode="HTML" /> <sling:encode value="${localeResource.locale.displayCountry}" mode="HTML" />
-                                <br/>
-                                <small>(<sling:encode value="${language.valueMap['jcr:language']}" mode="HTML" />)</small>
-                            </th>
-                        </c:if>
-                    </c:forEach>
-                </tr>
-            </thead>
-            <tbody>
-                <sling:adaptTo var="helper" adaptable="${slingRequest.requestPathInfo.suffixResource}" adaptTo="org.apache.sling.cms.core.models.i18nHelper" />
-                <c:forEach var="key" items="${helper.keys}">
-                    <tr>
-                        <td>
-                            <sling:encode value="${key}" mode="HTML" />
-                        </td>
-                        <c:forEach var="language" items="${sling:listChildren(slingRequest.requestPathInfo.suffixResource)}">
-                            <c:if test="${not empty language.valueMap['jcr:language']}">
-                                <td>
-                                    <c:set var="keyfound" value="false" />
-                                    <c:forEach var="entry" items="${sling:listChildren(language)}">
-                                        <c:if test="${entry.valueMap['sling:key'] == key}">
-                                            <c:set var="keyfound" value="true" />
-                                            <input name="${language.name}/${entry.name}/sling:message" class="input" type="text" value="${sling:encode(entry.valueMap['sling:message'],'HTML_ATTR')}" />
-                                            <input name="${language.name}/${entry.name}/sling:key" type="hidden" value="${key}" />
-                                        </c:if>
-                                    </c:forEach>
-                                    <c:if test="${keyfound == 'false'}">
-                                        <c:set var="rand" value="${helper.random}" />
-                                        <input name="${language.name}/entry_${rand}/sling:message" class="input" type="text" value="" />
-                                        <input name="${language.name}/entry_${rand}/sling:key" type="hidden" value="${key}" />
-                                        <input name="${language.name}/entry_${rand}/jcr:primaryType" type="hidden" value="sling:MessageEntry" />
-                                    </c:if>
-                                </td>
-                            </c:if>
-                        </c:forEach>
-                    </tr>
-                </c:forEach>
-            </tbody>
-        </table>
-        <button class="button is-primary">Save i18n Dictionary</button>
-    </fieldset>
-</form>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/thumbnail/thumbnail.jsp
similarity index 67%
copy from ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp
copy to ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/thumbnail/thumbnail.jsp
index 69e44fa..67d1e84 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/caconfig/edit/edit.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/thumbnail/thumbnail.jsp
@@ -16,7 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */ --%>
-<%@include file="/libs/sling-cms/global.jsp"%>
-<c:if test="${slingRequest.requestPathInfo.suffixResource != null}">
-    <sling:include resource="${slingRequest.requestPathInfo.suffixResource}" />
-</c:if>
\ No newline at end of file
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<div class="field">
+    <c:if test="${sling:getRelativeResource(slingRequest.requestPathInfo.suffixResource,properties.subpath) != null}">
+        <img src="${slingRequest.requestPathInfo.suffix}/${properties.subpath}${properties.suffix}" alt="${sling:encode(properties.alt,'HTML_ATTR')}" class="image ${properties.className}" />
+    </c:if>
+</div>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/pages/base/nav.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/pages/base/nav.jsp
index e9b3f8a..be4486e 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/pages/base/nav.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/pages/base/nav.jsp
@@ -19,7 +19,7 @@
 <%@include file="/libs/sling-cms/global.jsp"%>
 <nav class="navbar" role="navigation" aria-label="main mavigation">
     <div class="navbar-brand">
-        <a class="navbar-item" href="http://sling.apache.org" >
+        <a class="navbar-item" href="http://sling.apache.org">
             <img src="${branding.logo}" width="100" alt="${branding.appName}"/>
         </a>
         <a href="/cms/start.html" class="navbar-item" title="CMS Home">
@@ -41,6 +41,9 @@
                 <sling:adaptTo adaptable="${resourceResolver}" adaptTo="org.apache.sling.cms.AuthorizableWrapper" var="auth" />
                 <sling:getResource path="${auth.authorizable.path}/profile" var="profile" />
                 <a class="navbar-link">
+                    <c:if test="${sling:getRelativeResource(profile,'thumbnail') != null}">
+                        <img src="${profile.path}/thumbnail.transform/sling-cms-thumbnail32.png" alt="${resourceResolver.userID}" />
+                    </c:if>&nbsp;
                     <sling:encode value="${profile.valueMap.name}" default="${resourceResolver.userID}" mode="HTML" />
                 </a>
                 <div class="navbar-dropdown">
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/profile.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/profile.json
index 2680887..3a7baa3 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/profile.json
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/profile.json
@@ -20,6 +20,27 @@
                         "label": "Name",
                         "name": "profile/name"
                     },
+                    "thumbnail": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "sling-cms/components/editor/fields/text",
+                        "label": "Thumbnail",
+                        "name": "profile/thumbnail",
+                        "type": "file"
+                    },
+                    "thumbnailTypeHint": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+                        "name": "profile/thumbnail@TypeHint",
+                        "value": "sling:File"
+                    },
+                    "thumbnailPreview": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "sling-cms/components/editor/fields/thumbnail",
+                        "alt": "User Thumbnail",
+                        "className": "is-128x128",
+                        "subpath": "profile/thumbnail",
+                        "suffix": ".transform/sling-cms-thumbnail128.png"
+                    },
                     "email": {
                         "jcr:primaryType": "nt:unstructured",
                         "sling:resourceType": "sling-cms/components/editor/fields/text",
@@ -33,6 +54,24 @@
                         "label": "Locale",
                         "name": "profile/locale",
                         "optionsScript": "/libs/sling-cms/components/editor/scripts/localeOptions.jsp"
+                    },
+                    "defaultLayout": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "sling-cms/components/editor/fields/select",
+                        "label": "Default Layout",
+                        "name": "profile/defaultLayout",
+                        "options": {
+                            "grid": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "label": "Grid",
+                                "value": "grid"
+                            },
+                            "table": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "label": "Table",
+                                "value": "table"
+                            }
+                        }
                     }
                 }
             }
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/dictionary.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/dictionary.json
index 26675fe..330916f 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/dictionary.json
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/dictionary.json
@@ -2,15 +2,15 @@
     "jcr:primaryType": "sling:Page",
     "jcr:content": {
         "sling:resourceType": "sling-cms/components/pages/base",
-        "jcr:title": "i18n Dictionarties",
+        "jcr:title": "i18n Dictionaries",
         "jcr:primaryType": "nt:unstructured",
         "container": {
             "jcr:primaryType": "nt:unstructured",
-            "sling:resourceType": "sling-cms/components/general/container",
+            "sling:resourceType": "sling-cms/components/cms/container",
             "richtext": {
                 "jcr:primaryType": "nt:unstructured",
                 "sling:resourceType": "sling-cms/components/general/richtext",
-                "text": "<h3>i18n Dictionary</h3>"
+                "text": "<br/><h3>i18n Dictionary</h3><br/>"
             },
             "contentactions": {
                 "jcr:primaryType": "nt:unstructured",
@@ -23,62 +23,62 @@
                     }
                 }
             },
-            "contenttable": {
+            "i18ncontainer": {
                 "jcr:primaryType": "nt:unstructured",
-                "sling:resourceType": "sling-cms/components/cms/contenttable",
-                "columns": {
+                "sling:resourceType": "sling-cms/components/cms/i18ncontainer",
+                "contenttable": {
                     "jcr:primaryType": "nt:unstructured",
-                    "title": {
+                    "sling:resourceType": "sling-cms/components/cms/contenttable",
+                    "columns": {
                         "jcr:primaryType": "nt:unstructured",
-                        "title": "Language"
-                    },
-                    "language": {
-                        "jcr:primaryType": "nt:unstructured",
-                        "title": "i18n Code"
+                        "title": {
+                            "jcr:primaryType": "nt:unstructured",
+                            "title": "Language"
+                        },
+                        "language": {
+                            "jcr:primaryType": "nt:unstructured",
+                            "title": "i18n Code"
+                        },
+                        "actions": {
+                            "jcr:primaryType": "nt:unstructured",
+                            "title": "Actions"
+                        }
                     },
-                    "actions": {
-                        "jcr:primaryType": "nt:unstructured",
-                        "title": "Actions"
-                    }
-                },
-                "types": {
-                    "jcr:primaryType": "nt:unstructured",
-                    "sling:Folder": {
+                    "types": {
                         "jcr:primaryType": "nt:unstructured",
-                        "columns": {
+                        "sling:Folder": {
                             "jcr:primaryType": "nt:unstructured",
-                            "language": {
+                            "columns": {
                                 "jcr:primaryType": "nt:unstructured",
-                                "sling:resourceType": "sling-cms/components/cms/columns/localetitle"
-                            },
-                            "i18n": {
-                                "jcr:primaryType": "nt:unstructured",
-                                "sling:resourceType": "sling-cms/components/cms/columns/text",
-                                "property": "jcr:language"
-                            },
-                            "actions": {
-                                "jcr:primaryType": "nt:unstructured",
-                                "sling:resourceType": "sling-cms/components/cms/columns/actions",
-                                "delete": {
+                                "language": {
+                                    "jcr:primaryType": "nt:unstructured",
+                                    "sling:resourceType": "sling-cms/components/cms/columns/localetitle"
+                                },
+                                "i18n": {
                                     "jcr:primaryType": "nt:unstructured",
-                                    "modal": true,
-                                    "title": "Delete i18n Dictionary",
-                                    "icon": "trash",
-                                    "prefix": "/cms/shared/delete.html"
+                                    "sling:resourceType": "sling-cms/components/cms/columns/text",
+                                    "property": "jcr:language"
+                                },
+                                "actions": {
+                                    "jcr:primaryType": "nt:unstructured",
+                                    "sling:resourceType": "sling-cms/components/cms/columns/actions",
+                                    "delete": {
+                                        "jcr:primaryType": "nt:unstructured",
+                                        "modal": true,
+                                        "title": "Delete i18n Dictionary",
+                                        "icon": "trash",
+                                        "prefix": "/cms/shared/delete.html"
+                                    }
                                 }
                             }
                         }
                     }
+                },
+                "richtext_1": {
+                    "jcr:primaryType": "nt:unstructured",
+                    "sling:resourceType": "sling-cms/components/general/richtext",
+                    "text": "<br/><h3>i18n Dictionary Entries</h3><br/>"
                 }
-            },
-            "richtext_1": {
-                "jcr:primaryType": "nt:unstructured",
-                "sling:resourceType": "sling-cms/components/general/richtext",
-                "text": "<h3>i18n Dictionary Entries</h3>"
-            },
-            "i18ntable": {
-                "jcr:primaryType": "nt:unstructured",
-                "sling:resourceType": "sling-cms/components/cms/i18ntable"
             }
         }
     }
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/site/content.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/site/content.json
index 672c375..4040af6 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/content/site/content.json
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/site/content.json
@@ -10,6 +10,7 @@
             "contentactions": {
                 "jcr:primaryType": "nt:unstructured",
                 "sling:resourceType": "sling-cms/components/cms/contentactions",
+                "includeSwitcher": true,
                 "actions": {
                     "page": {
                         "jcr:primaryType": "nt:unstructured",
@@ -36,7 +37,7 @@
             },
             "contenttable": {
                 "jcr:primaryType": "nt:unstructured",
-                "sling:resourceType": "sling-cms/components/cms/contenttable",
+                "sling:resourceType": "sling-cms/components/cms/contentlayout",
                 "columns": {
                     "jcr:primaryType": "nt:unstructured",
                     "name": {
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/site/editgroup.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/site/editgroup.json
index e76236b..bf30b38 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/content/site/editgroup.json
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/site/editgroup.json
@@ -30,6 +30,12 @@
                         "name": "sling:configRef",
                         "required": false,
                         "type": "config"
+                    },
+                    "contentPrimaryType": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+                        "name": "jcr:content/jcr:primaryType",
+                        "value": "nt:unstructured"
                     }
                 }
             }
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/site/sites.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/site/sites.json
index 56e6176..e0d7a9c 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/content/site/sites.json
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/site/sites.json
@@ -10,6 +10,7 @@
             "contentactions": {
                 "jcr:primaryType": "nt:unstructured",
                 "sling:resourceType": "sling-cms/components/cms/contentactions",
+                "includeSwitcher": true,
                 "actions": {
                     "page": {
                         "jcr:primaryType": "nt:unstructured",
@@ -33,7 +34,7 @@
             },
             "contenttable": {
                 "jcr:primaryType": "nt:unstructured",
-                "sling:resourceType": "sling-cms/components/cms/contenttable",
+                "sling:resourceType": "sling-cms/components/cms/contentlayout",
                 "columns": {
                     "jcr:primaryType": "nt:unstructured",
                     "name": {
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json
index 43fe601..59e4751 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json
@@ -33,6 +33,8 @@
                         "jcr:primaryType": "nt:unstructured",
                         "alternatives": [
                             "/cms/site/content.html/content",
+                            "/cms/site/content.table.html/content",
+                            "/cms/site/content.grid.html/content",
                             "/cms/file/optimize.html"
                         ],
                         "link": "/cms/site/sites.html/content",
@@ -51,6 +53,7 @@
                     "config": {
                         "jcr:primaryType": "nt:unstructured",
                         "alternatives": [
+                            "/cms/transformations/edit.html",
                             "/cms/template/edit.html",
                             "/cms/config"
                         ],
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/transformations/create.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/transformations/create.json
new file mode 100644
index 0000000..6c15e49
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/transformations/create.json
@@ -0,0 +1,55 @@
+{
+    "jcr:primaryType": "sling:Page",
+    "jcr:content": {
+        "sling:resourceType": "sling-cms/components/pages/modal",
+        "jcr:title": "Add Transformation ",
+        "modalText": "Create Transformation",
+        "jcr:primaryType": "nt:unstructured",
+        "container": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/general/container",
+            "slingform": {
+                "jcr:primaryType": "nt:unstructured",
+                "sling:resourceType": "sling-cms/components/editor/slingform",
+                "actionSuffix": "/transformations/",
+                "button": "Create Transformation",
+                "fields": {
+                    "jcr:primaryType": "nt:unstructured",
+                    "sling:resourceType": "sling-cms/components/general/container",
+                    "transformationName": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "sling-cms/components/editor/fields/text",
+                        "label": "Transformation Name",
+                        "name": "name",
+                        "required": true,
+                        "skipload":true
+                    },
+                    "name": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "sling-cms/components/editor/fields/text",
+                        "label": "Node Name",
+                        "name": ":name"
+                    },
+                    "nameParam": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+                        "name": ":nameParam",
+                        "value": "name"
+                    },
+                    "primaryType": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+                        "name": "jcr:primaryType",
+                        "value": "nt:unstructured"
+                    },
+                    "resourceType": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+                        "name": "sling:resourceType",
+                        "value": "sling-cms/components/caconfig/transformation"
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/transformations/edit.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/transformations/edit.json
new file mode 100644
index 0000000..55b7174
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/transformations/edit.json
@@ -0,0 +1,27 @@
+{
+    "jcr:primaryType": "sling:Page",
+    "jcr:content": {
+        "sling:resourceType": "sling-cms/components/pages/base",
+        "jcr:title": "Configure Site",
+        "jcr:primaryType": "nt:unstructured",
+        "container": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/general/container",
+            "richtext": {
+                "jcr:primaryType": "nt:unstructured",
+                "sling:resourceType": "sling-cms/components/general/richtext",
+                "text": "<h2>Edit Transformation</h2>"
+            },
+            "contentbreadcrumb": {
+                "jcr:primaryType": "nt:unstructured",
+                "sling:resourceType": "sling-cms/components/cms/contentbreadcrumb",
+                "depth": 1,
+                "rootTitle": "Configuration"
+            },
+            "transformationeditor": {
+                "jcr:primaryType": "nt:unstructured",
+                "sling:resourceType": "sling-cms/components/caconfig/transformation"
+            }
+        }
+    }
+}
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/transformations/editor.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/transformations/editor.json
new file mode 100644
index 0000000..c686ccc
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/transformations/editor.json
@@ -0,0 +1,102 @@
+
+{
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/general/container",
+    "richtext": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/richtext",
+        "text": "<br/><h3>Transformations</h3>"
+    },
+    "contentactions": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/cms/contentactions",
+        "actions": {
+            "template": {
+                "jcr:primaryType": "nt:unstructured",
+                "label": "Transformation",
+                "prefix": "/cms/transformations/create.html"
+            }
+        }
+    },
+    "contenttable": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/cms/contenttable",
+        "appendSuffix": "/transformations",
+        "columns": {
+            "jcr:primaryType": "nt:unstructured",
+            "name": {
+                "jcr:primaryType": "nt:unstructured",
+                "title": "Name"
+            },
+            "title": {
+                "jcr:primaryType": "nt:unstructured",
+                "title": "Transformation Name"
+            },
+            "lastModified": {
+                "jcr:primaryType": "nt:unstructured",
+                "title": "Last Modified"
+            },
+            "actions": {
+                "jcr:primaryType": "nt:unstructured",
+                "title": "Actions"
+            }
+        },
+        "types": {
+            "jcr:primaryType": "nt:unstructured",
+            "nt:unstructured": {
+                "jcr:primaryType": "nt:unstructured",
+                "columns": {
+                    "jcr:primaryType": "nt:unstructured",
+                    "name": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "sling-cms/components/cms/columns/name",
+                        "link": false
+                    },
+                    "transformationName": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "sling-cms/components/cms/columns/text",
+                        "property": "name"
+                    },
+                    "lastModified": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "sling-cms/components/cms/columns/lastmodified",
+                        "subPath": ""
+                    },
+                    "actions": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "sling-cms/components/cms/columns/actions",
+                        "edit": {
+                            "jcr:primaryType": "nt:unstructured",
+                            "modal": false,
+                            "new": false,
+                            "title": "Edit Transformation",
+                            "icon": "pencil-f",
+                            "prefix": "/cms/transformations/edit.html"
+                        },
+                        "references": {
+                            "jcr:primaryType": "nt:unstructured",
+                            "modal": true,
+                            "title": "References",
+                            "icon": "directions",
+                            "prefix": "/cms/shared/references.html"
+                        },
+                        "movecopy": {
+                            "jcr:primaryType": "nt:unstructured",
+                            "modal": true,
+                            "title": "Move / Copy Transformation",
+                            "icon": "move-alt",
+                            "prefix": "/cms/shared/movecopy.html"
+                        },
+                        "delete": {
+                            "jcr:primaryType": "nt:unstructured",
+                            "modal": true,
+                            "title": "Delete Transformation",
+                            "icon": "trash",
+                            "prefix": "/cms/shared/delete.html"
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file