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

GET

/@content-types

Every content type with its settings.

GET

/@content-types/{type}

One content type with its settings.

POST

/@content-types

Create a Dexterity content type, optionally copying another one.

PATCH

/@content-types/{type}

Change some settings of a content type.

DELETE

/@content-types/{type}

Delete a Dexterity content type that has no items.

GET

/@content-types/{type}/workflow

The workflow of a content type.

POST

/@content-types/{type}/workflow

Change the workflow assignment of a content type.

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"
        },
        "behaviors": [
            {
                "title": "Full-Text Indexing",
                "token": "plone.textindexer"
            },
            {
                "title": "Event Basic",
                "token": "plone.eventbasic"
            },
            {
                "title": "Event Recurrence",
                "token": "plone.eventrecurrence"
            },
            {
                "title": "Event Location",
                "token": "plone.eventlocation"
            },
            {
                "title": "Event Attendees",
                "token": "plone.eventattendees"
            },
            {
                "title": "Event Contact",
                "token": "plone.eventcontact"
            },
            {
                "title": "Dublin Core metadata",
                "token": "plone.dublincore"
            },
            {
                "title": "Name from title",
                "token": "plone.namefromtitle"
            },
            {
                "title": "RichText",
                "token": "plone.richtext"
            },
            {
                "title": "Allow discussion",
                "token": "plone.allowdiscussion"
            },
            {
                "title": "Exclude From navigation",
                "token": "plone.excludefromnavigation"
            },
            {
                "title": "Short name",
                "token": "plone.shortname"
            },
            {
                "title": "Related items",
                "token": "plone.relateditems"
            },
            {
                "title": "Versioning",
                "token": "plone.versioning"
            },
            {
                "title": "Locking",
                "token": "plone.locking"
            }
        ],
        "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": [
                    "behaviors"
                ],
                "id": "behaviors",
                "title": "Behaviors"
            },
            {
                "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"
                }
            },
            "behaviors": {
                "additionalItems": true,
                "default": [],
                "description": "The complete list of the type's behaviors. Their order sets where their fields appear in the edit form.",
                "factory": "Multiple Choice",
                "items": {
                    "description": "",
                    "factory": "Choice",
                    "title": "",
                    "type": "string",
                    "vocabulary": {
                        "@id": "http://localhost:55001/plone/@vocabularies/plone.restapi.Behaviors"
                    }
                },
                "title": "Behaviors",
                "type": "array",
                "uniqueItems": true
            },
            "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": {
        "chain": [
            "simple_publication_workflow"
        ],
        "follows_default": true
    }
}

Property

Description

@id

URL of the content type in this service.

id

Content type ID.

title

Translated title.

content_count

Number of items of this type, including those the current user cannot view.

schema

JSON schema of the settings, with properties, required, and fieldsets

data

Current values of the settings, keyed by field name.

workflow

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.chain lists the IDs of the workflows of the type's chain, in order, and is empty when the type has no workflow. The first workflow sets the state of items of the type. 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. It changes through /@content-types/{type}/workflow, described in Change the workflow assignment.

Core settings#

Fieldset

Field

Value

general

title

Title, not empty.

general

description

Description.

contents

global_allow

Whether items of this type can be added inside items of the types that allow every type, with allowed_content_types_mode set to all.

contents

allowed_content_types_mode

Which types can be added inside items of this type: all, selected, or none.

contents

allowed_content_types

IDs of the selected types, from the plone.app.vocabularies.PortalTypes vocabulary.

visibility

searchable

Whether items of this type are shown in search results.

visibility

default_page

Whether items of this type can be a folder's default page.

visibility

in_navigation

Whether items of this type are shown in the site's navigation.

comments

allow_discussion

Whether items of this type allow comments. Only when plone.app.discussion is installed.

behaviors

behaviors

IDs of the type's behaviors, in order. Dexterity types only.

versioning

versioning

off, manual, or automatic.

allowed_content_types_mode and allowed_content_types work together:

  • all allows every type. allowed_content_types keeps the selection, which applies when you change the mode to selected later.

  • selected allows only the types in allowed_content_types, which can't be empty.

  • none allows no types, and allowed_content_types must 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.

behaviors is the complete, ordered list of the type's behaviors. Their order sets where their fields appear in the edit form. Behaviors are listed by their name, such as plone.dublincore, also when the type stores the interface identifier. A PATCH replaces the list: a behavior left out is disabled. Behaviors stored on the type that aren't registered, for example those of an uninstalled add-on, aren't listed and are kept. In a PATCH that changes behaviors and versioning, the versioning setting decides whether the type has the versioning behavior.

http

PATCH /plone/@content-types/Event HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
Content-Type: application/json

{
    "behaviors": [
        "plone.eventbasic",
        "plone.dublincore",
        "plone.namefromtitle",
        "plone.leadimage"
    ]
}

curl

curl -i -X PATCH http://nohost/plone/@content-types/Event -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"behaviors": ["plone.eventbasic", "plone.dublincore", "plone.namefromtitle", "plone.leadimage"]}' --user admin:secret

httpie

echo '{
  "behaviors": [
    "plone.eventbasic",
    "plone.dublincore",
    "plone.namefromtitle",
    "plone.leadimage"
  ]
}' | http PATCH http://nohost/plone/@content-types/Event Accept:application/json Content-Type:application/json -a admin:secret

python-requests

requests.patch('http://nohost/plone/@content-types/Event', headers={'Accept': 'application/json', 'Content-Type': 'application/json'}, json={'behaviors': ['plone.eventbasic', 'plone.dublincore', 'plone.namefromtitle', 'plone.leadimage']}, auth=('admin', 'secret'))

Use the Behaviors service to list the behaviors and the fields they add, and the behaviors parameter of the Types service to preview the edit form with other behaviors.

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.

Change the workflow assignment#

Send a GET request to /@content-types/{type}/workflow to get the workflow of a content type.

http

GET /plone/@content-types/Event/workflow HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0

curl

curl -i -X GET http://nohost/plone/@content-types/Event/workflow -H "Accept: application/json" --user admin:secret

httpie

http http://nohost/plone/@content-types/Event/workflow Accept:application/json -a admin:secret

python-requests

requests.get('http://nohost/plone/@content-types/Event/workflow', headers={'Accept': 'application/json'}, auth=('admin', 'secret'))
HTTP/1.1 200 OK
Content-Type: application/json

{
    "@id": "http://localhost:55001/plone/@content-types/Event/workflow",
    "chain": [
        "simple_publication_workflow"
    ],
    "follows_default": true
}

It has the same chain and follows_default as the workflow of the content type.

Send a POST request to /@content-types/{type}/workflow to change it. The body has these properties:

Property

Description

workflow

ID of the new workflow, or null for no workflow.

follows_default

When true, the type follows the default workflow again, and workflow is ignored.

state_map

Maps the states of the current workflow to states of the new one. Use "" for items of a type without a workflow. Optional.

dry_run

When true, changes nothing, and returns the planned change.

The body needs workflow or follows_default. Assigning a workflow replaces the whole chain, so the type has only that workflow afterwards. The state mapping and the counts of items by state use the first workflow of the current chain. Unknown workflows and unknown target states return 400 Bad Request. The service changes one content type at a time. Use the Workflows service to list the workflows, their states and who can see items in them, and to change the default workflow assignment.

Plan the change#

With dry_run, the service returns the number of items of the type in each state, and a suggested new state for each.

http

POST /plone/@content-types/Event/workflow HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
Content-Type: application/json

{
    "dry_run": true,
    "workflow": "intranet_workflow"
}

curl

curl -i -X POST http://nohost/plone/@content-types/Event/workflow -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"dry_run": true, "workflow": "intranet_workflow"}' --user admin:secret

httpie

echo '{
  "dry_run": true,
  "workflow": "intranet_workflow"
}' | http POST http://nohost/plone/@content-types/Event/workflow Accept:application/json Content-Type:application/json -a admin:secret

python-requests

requests.post('http://nohost/plone/@content-types/Event/workflow', headers={'Accept': 'application/json', 'Content-Type': 'application/json'}, json={'dry_run': True, 'workflow': 'intranet_workflow'}, auth=('admin', 'secret'))
HTTP/1.1 200 OK
Content-Type: application/json

{
    "current": "simple_publication_workflow",
    "states": [
        {
            "count": 0,
            "id": "pending",
            "suggested": "private"
        },
        {
            "count": 1,
            "id": "private",
            "suggested": "private"
        },
        {
            "count": 1,
            "id": "published",
            "suggested": "external"
        }
    ],
    "unaffected": 0,
    "workflow": "intranet_workflow"
}

Property

Description

workflow

ID of the new workflow, which is the default workflow with follows_default, or null.

current

ID of the current workflow, or null.

states

One entry per state of the current workflow, in its order, with the state id, the count of items in it, and the suggested new state.

unaffected

Number of items that a placeful workflow policy gives another workflow, which the change doesn't affect.

Without a current workflow, states has a single entry with an empty id, counting every item of the type. The counts include items the current user cannot view. suggested is null when the new workflow is null. The suggested state keeps who can see the items, as described in Workflows.

Apply the change#

Without dry_run, the service assigns the workflow, moves the items of the type to their new states, and updates their security, as the classic "Content Settings" control panel does. States left out of state_map get the suggested state. The change happens during the request, so it can take long for types with many items.

http

POST /plone/@content-types/Event/workflow HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
Content-Type: application/json

{
    "state_map": {
        "private": "internal"
    },
    "workflow": "intranet_workflow"
}

curl

curl -i -X POST http://nohost/plone/@content-types/Event/workflow -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"state_map": {"private": "internal"}, "workflow": "intranet_workflow"}' --user admin:secret

httpie

echo '{
  "state_map": {
    "private": "internal"
  },
  "workflow": "intranet_workflow"
}' | http POST http://nohost/plone/@content-types/Event/workflow Accept:application/json Content-Type:application/json -a admin:secret

python-requests

requests.post('http://nohost/plone/@content-types/Event/workflow', headers={'Accept': 'application/json', 'Content-Type': 'application/json'}, json={'state_map': {'private': 'internal'}, 'workflow': 'intranet_workflow'}, auth=('admin', 'secret'))
HTTP/1.1 200 OK
Content-Type: application/json

{
    "changed": 2,
    "follows_default": false,
    "workflow": "intranet_workflow"
}

The response contains the new workflow, whether the type follows_default, and the number of changed items. When the workflow stays the same, for example when a type following the default gets the default workflow explicitly, only the assignment changes, and changed is 0.

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"
        },
        "behaviors": [
            {
                "title": "Dublin Core metadata",
                "token": "plone.dublincore"
            },
            {
                "title": "Name from title",
                "token": "plone.namefromtitle"
            }
        ],
        "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": [
                    "behaviors"
                ],
                "id": "behaviors",
                "title": "Behaviors"
            },
            {
                "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"
                }
            },
            "behaviors": {
                "additionalItems": true,
                "default": [],
                "description": "The complete list of the type's behaviors. Their order sets where their fields appear in the edit form.",
                "factory": "Multiple Choice",
                "items": {
                    "description": "",
                    "factory": "Choice",
                    "title": "",
                    "type": "string",
                    "vocabulary": {
                        "@id": "http://localhost:55001/plone/@vocabularies/plone.restapi.Behaviors"
                    }
                },
                "title": "Behaviors",
                "type": "array",
                "uniqueItems": true
            },
            "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": {
        "chain": [
            "simple_publication_workflow"
        ],
        "follows_default": true
    }
}

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

title

Fieldset title.

order

Sort order of the fieldset, lowest first. The core fieldsets use 10 to 50.

schema

zope.schema interface declaring the fields. Field names must be unique across all fieldsets.

permission

Permission ID needed to see and change the fieldset, or None.

enabled()

Return whether to show the fieldset, for example only when the add-on that uses it is installed. The base class returns True.

read()

Return the current values.

validate(data)

Return a list of errors for the changed values, each with field and message.

write(data)

Store the changed values.

remove()

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"
    />