Content type management#
The @content-types service lists, configures, creates, copies, and deletes content types.
It is available at the site root, and requires the Plone Site Setup: Types permission, like the content settings control panel, as well as permission to use the REST API.
Use the Types service to edit the schema of a content type.
The dexterity-types control panel of the Control Panels service remains available.
See also
This page used to show example representations of the default content types. They are now in Representations of the default content types.
Method |
Path |
Result |
|---|---|---|
|
|
Every content type with its settings. |
|
|
One content type with its settings. |
|
|
Create a Dexterity content type, optionally copying another one. |
|
|
Change some settings of a content type. |
|
|
Delete a Dexterity content type that has no items. |
Encode content type IDs in URLs, for example News%20Item.
Unknown content types and extra path segments return 404 Not Found.
List content types#
Send a GET request to /@content-types to list every content type with its settings.
http
GET /plone/@content-types HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
curl
curl -i -X GET http://nohost/plone/@content-types -H "Accept: application/json" --user admin:secret
httpie
http http://nohost/plone/@content-types Accept:application/json -a admin:secret
python-requests
requests.get('http://nohost/plone/@content-types', headers={'Accept': 'application/json'}, auth=('admin', 'secret'))
The response contains @id, items, and items_total.
Each item has the representation of a single content type, described below.
Get a content type#
Send a GET request to /@content-types/{type} to get one content type.
http
GET /plone/@content-types/Event HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
curl
curl -i -X GET http://nohost/plone/@content-types/Event -H "Accept: application/json" --user admin:secret
httpie
http http://nohost/plone/@content-types/Event Accept:application/json -a admin:secret
python-requests
requests.get('http://nohost/plone/@content-types/Event', headers={'Accept': 'application/json'}, auth=('admin', 'secret'))
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:55001/plone/@content-types/Event",
"content_count": 0,
"data": {
"allow_discussion": false,
"allowed_content_types": [],
"allowed_content_types_mode": {
"title": "No types",
"token": "none"
},
"default_page": true,
"description": "Events can be shown in calendars.",
"global_allow": true,
"in_navigation": true,
"searchable": true,
"title": "Event",
"versioning": {
"title": "Automatic",
"token": "automatic"
}
},
"id": "Event",
"schema": {
"fieldsets": [
{
"fields": [
"title",
"description"
],
"id": "general",
"title": "General"
},
{
"fields": [
"global_allow",
"allowed_content_types_mode",
"allowed_content_types"
],
"id": "contents",
"title": "Contents"
},
{
"fields": [
"searchable",
"default_page",
"in_navigation"
],
"id": "visibility",
"title": "Visibility"
},
{
"fields": [
"allow_discussion"
],
"id": "comments",
"title": "Comments"
},
{
"fields": [
"versioning"
],
"id": "versioning",
"title": "Versioning"
}
],
"properties": {
"allow_discussion": {
"description": "",
"factory": "Yes/No",
"title": "Allow comments",
"type": "boolean"
},
"allowed_content_types": {
"additionalItems": true,
"default": [],
"description": "Kept while all types are allowed",
"factory": "Multiple Choice",
"items": {
"description": "",
"factory": "Choice",
"title": "",
"type": "string",
"vocabulary": {
"@id": "http://localhost:55001/plone/@vocabularies/plone.app.vocabularies.PortalTypes"
}
},
"title": "Selected types",
"type": "array",
"uniqueItems": true
},
"allowed_content_types_mode": {
"choices": [
[
"all",
"All types"
],
[
"selected",
"Selected types"
],
[
"none",
"No types"
]
],
"description": "Whether all types, only the selected types, or no types can be added inside content of this type",
"enum": [
"all",
"selected",
"none"
],
"enumNames": [
"All types",
"Selected types",
"No types"
],
"factory": "Choice",
"title": "Allowed content types",
"type": "string",
"vocabulary": {
"@id": "http://localhost:55001/plone/@sources/allowed_content_types_mode"
}
},
"default_page": {
"description": "Content of this type can be a folder's default page",
"factory": "Yes/No",
"title": "Usable as a default page",
"type": "boolean"
},
"description": {
"default": "",
"description": "",
"factory": "Text",
"title": "Description",
"type": "string",
"widget": "textarea"
},
"global_allow": {
"description": "Allow adding content of this type in any folder",
"factory": "Yes/No",
"title": "Globally addable",
"type": "boolean"
},
"in_navigation": {
"description": "Items of this type are listed in the site's navigation",
"factory": "Yes/No",
"title": "Shown in navigation",
"type": "boolean"
},
"searchable": {
"description": "",
"factory": "Yes/No",
"title": "Shown in search results",
"type": "boolean"
},
"title": {
"description": "",
"factory": "Text line (String)",
"minLength": 1,
"title": "Title",
"type": "string"
},
"versioning": {
"choices": [
[
"off",
"Off"
],
[
"manual",
"Manual"
],
[
"automatic",
"Automatic"
]
],
"description": "Manual versioning saves a version on request, automatic versioning also on every edit",
"enum": [
"off",
"manual",
"automatic"
],
"enumNames": [
"Off",
"Manual",
"Automatic"
],
"factory": "Choice",
"title": "Versioning",
"type": "string",
"vocabulary": {
"@id": "http://localhost:55001/plone/@sources/versioning"
}
}
},
"required": [
"title",
"global_allow",
"allowed_content_types_mode",
"searchable",
"default_page",
"in_navigation",
"allow_discussion",
"versioning"
],
"type": "object"
},
"title": "Event",
"workflow": {
"follows_default": true,
"id": "simple_publication_workflow"
}
}
Property |
Description |
|---|---|
|
URL of the content type in this service. |
|
Content type ID. |
|
Translated title. |
|
Number of items of this type, including those the current user cannot view. |
|
JSON schema of the settings, with |
|
Current values of the settings, keyed by field name. |
|
Read-only summary of the assigned workflow. |
The schema has the format described in Types Schema.
Each fieldset has an id, a title, and the names of its fields.
Fields marked readOnly in the schema cannot be changed.
Choice values have a token and a title, like in control panels and content.
workflow.id is the first workflow of the type's chain, or null when the type has no workflow.
workflow.follows_default is true when the type uses the site's default workflow, and false when a workflow, or no workflow, is assigned to it explicitly.
Core settings#
Fieldset |
Field |
Value |
|---|---|---|
|
|
Title, not empty. |
|
|
Description. |
|
|
Whether items of this type can be added inside items of the types that allow every type, with |
|
|
Which types can be added inside items of this type: |
|
|
IDs of the selected types, from the |
|
|
Whether items of this type are shown in search results. |
|
|
Whether items of this type can be a folder's default page. |
|
|
Whether items of this type are shown in the site's navigation. |
|
|
Whether items of this type allow comments. Only when |
|
|
|
allowed_content_types_mode and allowed_content_types work together:
allallows every type.allowed_content_typeskeeps the selection, which applies when you change the mode toselectedlater.selectedallows only the types inallowed_content_types, which can't be empty.noneallows no types, andallowed_content_typesmust be empty.
The service checks these rules against the stored values too, so changing only the mode to none fails while types are selected.
manual versioning saves versions on request, and automatic versioning also saves a version on every edit.
Changing the versioning updates the repository policies of the type and its versioning behavior.
Enabling versioning also enables locking, and disabling versioning keeps locking.
Repository policies added by add-ons are kept.
Change settings#
Send a PATCH request to /@content-types/{type} with the fields to change.
Fields you don't send keep their values.
http
PATCH /plone/@content-types/conference_event HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
Content-Type: application/json
{
"allowed_content_types": [
"Image",
"File"
],
"allowed_content_types_mode": "selected",
"global_allow": false,
"searchable": true,
"versioning": "manual"
}
curl
curl -i -X PATCH http://nohost/plone/@content-types/conference_event -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"allowed_content_types": ["Image", "File"], "allowed_content_types_mode": "selected", "global_allow": false, "searchable": true, "versioning": "manual"}' --user admin:secret
httpie
echo '{
"allowed_content_types": [
"Image",
"File"
],
"allowed_content_types_mode": "selected",
"global_allow": false,
"searchable": true,
"versioning": "manual"
}' | http PATCH http://nohost/plone/@content-types/conference_event Accept:application/json Content-Type:application/json -a admin:secret
python-requests
requests.patch('http://nohost/plone/@content-types/conference_event', headers={'Accept': 'application/json', 'Content-Type': 'application/json'}, json={'allowed_content_types': ['Image', 'File'], 'allowed_content_types_mode': 'selected', 'global_allow': False, 'searchable': True, 'versioning': 'manual'}, auth=('admin', 'secret'))
HTTP/1.1 204 No Content
A choice value can be its token, or an object with a token, as returned in data.
The service validates every change before it stores any of them.
Invalid values, unknown fields, and read-only fields return 400 Bad Request with the field names and messages.
Create a content type#
Send a POST request to /@content-types to create a Dexterity container type with an empty schema.
The body requires a title, and accepts an id and a description.
Without an id, the service derives one from the title.
http
POST /plone/@content-types HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
Content-Type: application/json
{
"description": "An event at a conference.",
"title": "Conference event"
}
curl
curl -i -X POST http://nohost/plone/@content-types -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"description": "An event at a conference.", "title": "Conference event"}' --user admin:secret
httpie
echo '{
"description": "An event at a conference.",
"title": "Conference event"
}' | http POST http://nohost/plone/@content-types Accept:application/json Content-Type:application/json -a admin:secret
python-requests
requests.post('http://nohost/plone/@content-types', headers={'Accept': 'application/json', 'Content-Type': 'application/json'}, json={'description': 'An event at a conference.', 'title': 'Conference event'}, auth=('admin', 'secret'))
HTTP/1.1 201 Created
Content-Type: application/json
Location: http://localhost:55001/plone/@content-types/conference_event
{
"@id": "http://localhost:55001/plone/@content-types/conference_event",
"content_count": 0,
"data": {
"allow_discussion": false,
"allowed_content_types": [],
"allowed_content_types_mode": {
"title": "No types",
"token": "none"
},
"default_page": false,
"description": "An event at a conference.",
"global_allow": true,
"in_navigation": false,
"searchable": true,
"title": "Conference event",
"versioning": {
"title": "Off",
"token": "off"
}
},
"id": "conference_event",
"schema": {
"fieldsets": [
{
"fields": [
"title",
"description"
],
"id": "general",
"title": "General"
},
{
"fields": [
"global_allow",
"allowed_content_types_mode",
"allowed_content_types"
],
"id": "contents",
"title": "Contents"
},
{
"fields": [
"searchable",
"default_page",
"in_navigation"
],
"id": "visibility",
"title": "Visibility"
},
{
"fields": [
"allow_discussion"
],
"id": "comments",
"title": "Comments"
},
{
"fields": [
"versioning"
],
"id": "versioning",
"title": "Versioning"
}
],
"properties": {
"allow_discussion": {
"description": "",
"factory": "Yes/No",
"title": "Allow comments",
"type": "boolean"
},
"allowed_content_types": {
"additionalItems": true,
"default": [],
"description": "Kept while all types are allowed",
"factory": "Multiple Choice",
"items": {
"description": "",
"factory": "Choice",
"title": "",
"type": "string",
"vocabulary": {
"@id": "http://localhost:55001/plone/@vocabularies/plone.app.vocabularies.PortalTypes"
}
},
"title": "Selected types",
"type": "array",
"uniqueItems": true
},
"allowed_content_types_mode": {
"choices": [
[
"all",
"All types"
],
[
"selected",
"Selected types"
],
[
"none",
"No types"
]
],
"description": "Whether all types, only the selected types, or no types can be added inside content of this type",
"enum": [
"all",
"selected",
"none"
],
"enumNames": [
"All types",
"Selected types",
"No types"
],
"factory": "Choice",
"title": "Allowed content types",
"type": "string",
"vocabulary": {
"@id": "http://localhost:55001/plone/@sources/allowed_content_types_mode"
}
},
"default_page": {
"description": "Content of this type can be a folder's default page",
"factory": "Yes/No",
"title": "Usable as a default page",
"type": "boolean"
},
"description": {
"default": "",
"description": "",
"factory": "Text",
"title": "Description",
"type": "string",
"widget": "textarea"
},
"global_allow": {
"description": "Allow adding content of this type in any folder",
"factory": "Yes/No",
"title": "Globally addable",
"type": "boolean"
},
"in_navigation": {
"description": "Items of this type are listed in the site's navigation",
"factory": "Yes/No",
"title": "Shown in navigation",
"type": "boolean"
},
"searchable": {
"description": "",
"factory": "Yes/No",
"title": "Shown in search results",
"type": "boolean"
},
"title": {
"description": "",
"factory": "Text line (String)",
"minLength": 1,
"title": "Title",
"type": "string"
},
"versioning": {
"choices": [
[
"off",
"Off"
],
[
"manual",
"Manual"
],
[
"automatic",
"Automatic"
]
],
"description": "Manual versioning saves a version on request, automatic versioning also on every edit",
"enum": [
"off",
"manual",
"automatic"
],
"enumNames": [
"Off",
"Manual",
"Automatic"
],
"factory": "Choice",
"title": "Versioning",
"type": "string",
"vocabulary": {
"@id": "http://localhost:55001/plone/@sources/versioning"
}
}
},
"required": [
"title",
"global_allow",
"allowed_content_types_mode",
"searchable",
"default_page",
"in_navigation",
"allow_discussion",
"versioning"
],
"type": "object"
},
"title": "Conference event",
"workflow": {
"follows_default": true,
"id": "simple_publication_workflow"
}
}
The new type has the same defaults as one created in the classic "Dexterity Content Types" control panel.
The service returns 409 Conflict when a type with the ID exists.
Copy a content type#
Add copy_from with the ID of a Dexterity content type to copy it.
The copy has the schema, behaviors, settings, actions, repository policies, and workflow assignment of the source.
Without a description, it also has the source's description.
http
POST /plone/@content-types HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
Content-Type: application/json
{
"copy_from": "Event",
"description": "An event at a conference.",
"id": "ConferenceEvent",
"title": "Conference event"
}
curl
curl -i -X POST http://nohost/plone/@content-types -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"copy_from": "Event", "description": "An event at a conference.", "id": "ConferenceEvent", "title": "Conference event"}' --user admin:secret
httpie
echo '{
"copy_from": "Event",
"description": "An event at a conference.",
"id": "ConferenceEvent",
"title": "Conference event"
}' | http POST http://nohost/plone/@content-types Accept:application/json Content-Type:application/json -a admin:secret
python-requests
requests.post('http://nohost/plone/@content-types', headers={'Accept': 'application/json', 'Content-Type': 'application/json'}, json={'copy_from': 'Event', 'description': 'An event at a conference.', 'id': 'ConferenceEvent', 'title': 'Conference event'}, auth=('admin', 'secret'))
The response is the new content type, like the response to creating one. Changes to the copy, including to its schema, don't affect the source.
Delete a content type#
Send a DELETE request to /@content-types/{type} to delete a Dexterity content type.
http
DELETE /plone/@content-types/conference_event HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
curl
curl -i -X DELETE http://nohost/plone/@content-types/conference_event -H "Accept: application/json" --user admin:secret
httpie
http DELETE http://nohost/plone/@content-types/conference_event Accept:application/json -a admin:secret
python-requests
requests.delete('http://nohost/plone/@content-types/conference_event', headers={'Accept': 'application/json'}, auth=('admin', 'secret'))
HTTP/1.1 204 No Content
Deleting a type also removes its settings, its workflow assignment, and its use as an allowed type of other types. A type created later with the same ID starts with default settings.
While items of the type exist, the service returns 409 Conflict with their number.
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"content_count": 1,
"error": {
"message": "The content type still has items",
"type": "Conflict"
}
}
Add settings from an add-on#
Each fieldset comes from a named adapter providing plone.restapi.typesettings.interfaces.ITypeSettings.
The core fieldsets use the same mechanism, so an add-on can add its own settings to every content type.
Because the response includes a JSON schema, a frontend can show them without knowing the add-on.
An adapter adapts the FTI and the request, and has these attributes and methods:
Name |
Description |
|---|---|
|
Fieldset title. |
|
Sort order of the fieldset, lowest first. The core fieldsets use 10 to 50. |
|
|
|
Permission ID needed to see and change the fieldset, or |
|
Return whether to show the fieldset, for example only when the add-on that uses it is installed. The base class returns |
|
Return the current values. |
|
Return a list of errors for the changed values, each with |
|
Store the changed values. |
|
Delete what the settings store outside the FTI, before the type is deleted. |
The service converts values with the registered field serializers and deserializers, and validates them with the field constraints and the schema invariants.
It applies plone.autoform directives such as omitted, mode, and order_before.
Use flat fields: values of schema.Object fields aren't converted to and from JSON.
Subclass plone.restapi.typesettings.settings.TypeSettings to store settings as FTI properties, or override its methods to store them elsewhere.
This example stores a setting in the registry:
from plone.registry.interfaces import IRegistry
from plone.restapi.typesettings.settings import TypeSettings
from zope import schema
from zope.component import getUtility
from zope.interface import Interface
class ISponsorSettings(Interface):
sponsors_enabled = schema.Bool(title="Show sponsors")
class SponsorSettings(TypeSettings):
title = "Sponsors"
order = 100
schema = ISponsorSettings
record = "collective.sponsors.types"
def read(self):
types = getUtility(IRegistry)[self.record]
return {"sponsors_enabled": self.context.getId() in types}
def write(self, data):
registry = getUtility(IRegistry)
types = set(registry[self.record]) - {self.context.getId()}
if data["sponsors_enabled"]:
types.add(self.context.getId())
registry[self.record] = sorted(types)
def remove(self):
self.write({"sponsors_enabled": False})
<adapter
factory=".settings.SponsorSettings"
name="collective.sponsors"
/>