diff --git a/cmo-cli/src/main/config/classifications/derivate_types.xml b/cmo-cli/src/main/config/classifications/derivate_types.xml
new file mode 100644
index 0000000..c02cb01
--- /dev/null
+++ b/cmo-cli/src/main/config/classifications/derivate_types.xml
@@ -0,0 +1,18 @@
+
+
+ * The type is used to distinguish the different file areas of a derivate (e.g. the actual
+ * content or a TEI edition). The single supported classification is {@code derivate_types}.
+ */
+@Path("cmo/derivate/")
+public class CMODerivateResource {
+
+ /**
+ * The classification which holds the possible derivate types.
+ */
+ public static final String DERIVATE_TYPES_CLASSIFICATION = "derivate_types";
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ /**
+ * Sets the {@code derivate_types} category of the given derivate to the given type and
+ * redirects back to the page the request came from (or to the owning object page).
+ *
+ * @param derivateID the id of the derivate to change
+ * @param type the category id inside the {@link #DERIVATE_TYPES_CLASSIFICATION} classification
+ * @param request the current request, used to determine the redirect target
+ * @return a redirect response back to the referring page
+ */
+ @Path("{derid}/set-type/{type}")
+ @GET
+ public Response setType(@PathParam("derid") String derivateID, @PathParam("type") String type,
+ @Context HttpServletRequest request) {
+ if (!MCRObjectID.isValid(derivateID)) {
+ return Response.status(Response.Status.BAD_REQUEST).entity("Invalid derivate id " + derivateID).build();
+ }
+
+ MCRObjectID derId = MCRObjectID.getInstance(derivateID);
+ if (!MCRMetadataManager.exists(derId)) {
+ return Response.status(Response.Status.NOT_FOUND).entity("Derivate " + derivateID + " does not exist")
+ .build();
+ }
+
+ if (!MCRAccessManager.checkPermission(derId, MCRAccessManager.PERMISSION_WRITE)) {
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity("No permission to write derivate " + derivateID).build();
+ }
+
+ MCRCategoryID categoryID = new MCRCategoryID(DERIVATE_TYPES_CLASSIFICATION, type);
+ if (!MCRCategoryDAOFactory.getInstance().exist(categoryID)) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity("Unknown derivate type " + type).build();
+ }
+
+ MCRDerivate derivate = MCRMetadataManager.retrieveMCRDerivate(derId);
+ MCRObjectDerivate objectDerivate = derivate.getDerivate();
+
+ // replace any previously set derivate type
+ objectDerivate.getClassifications()
+ .removeIf(classification -> DERIVATE_TYPES_CLASSIFICATION.equals(classification.getClassId()));
+ objectDerivate.getClassifications()
+ .add(new MCRMetaClassification("classification", 0, null, categoryID));
+
+ try {
+ MCRMetadataManager.update(derivate);
+ } catch (MCRAccessException e) {
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity("No permission to write derivate " + derivateID).build();
+ }
+
+ LOGGER.info("Set derivate type of {} to {}", derivateID, type);
+ return Response.seeOther(getRedirectTarget(request, derivate.getOwnerID())).build();
+ }
+
+ private URI getRedirectTarget(HttpServletRequest request, MCRObjectID ownerID) {
+ String referer = request.getHeader("Referer");
+ if (referer != null && !referer.isBlank()) {
+ return URI.create(referer);
+ }
+ return URI.create(MCRFrontendUtil.getBaseURL() + "receive/" + ownerID);
+ }
+
+}
diff --git a/cmo-module/src/main/resources/xsl/objectActions.xsl b/cmo-module/src/main/resources/xsl/objectActions.xsl
index e0b898b..0a50005 100644
--- a/cmo-module/src/main/resources/xsl/objectActions.xsl
+++ b/cmo-module/src/main/resources/xsl/objectActions.xsl
@@ -422,6 +422,32 @@
+