Create content in one step#

This guide shows you how to use the operations of plone_client.operations, which each send several requests in the right order: create a page from Markdown, create an item with its images, upload a folder, and copy or move content with a report. Every operation is also a command of the command line.

Create a page from Markdown#

Install the markdown extra, which needs Python 3.12 or later.

uv add "plone-client[markdown]"

Pass the Markdown, and the folder to create the page in. A leading # heading becomes the title.

from pathlib import Path
from plone_client import operations

result = operations.create_page(
    client, "/docs", Path("getting-started.md").read_text(), publish="publish"
)
print(result.item["@id"])
plone-client create-page /docs getting-started.md --publish publish

For HTML, pass source_format="html" in Python, or --html on the command line; a file ending in .html is read as HTML. To give the title yourself, pass title, or --title.

Create an item with its images#

Pass the files to upload inside the item. The first image becomes the lead image, stored the way the type expects: in its image field, or in preview_image_link, as News Items on Volto sites do.

result = operations.create_with_files(
    client,
    "/news",
    "News Item",
    ["lead.jpg", "inside.jpg", "plan.pdf"],
    title="Our new office",
    publish="publish",
)
plone-client create-with-files /news "News Item" lead.jpg inside.jpg plan.pdf --title "Our new office" --publish publish

To pick another lead image, pass its index in lead_image, counting from 0, or its position with --lead, counting from 1. To set none, pass lead_image=None (--no-lead).

Upload a folder#

The contents of the local folder go inside the site’s folder: subfolders become pages, images become Image items, and other files File items. With convert_pages (--pages), Markdown and HTML files become pages, and a folder’s index.md or README.md becomes the content of the folder’s page.

result = operations.upload_tree(client, "handbook", "/docs", convert_pages=True)
for entry in result.created:
    print(entry["source"], "->", entry["@id"])
plone-client upload-tree handbook /docs --pages

Files and folders whose names start with a dot are left out, unless you pass include_hidden=True (--hidden).

Copy or move a folder#

copy_items() and move_items() report where each item went and how many items it holds, before and after.

result = operations.copy_items(client, "/news/2025", "/archive")
for entry in result.created:
    print(entry["@id"], entry["items"], entry["items_after"])

Plone renames a copy whose id is taken, so read the new @id from the result.

Preview an operation#

Run it with a dry_run client, or pass -n on the command line. The result lists the requests it would send, and the items it would create, with "planned": true and the path they would most likely get.

plone-client -n upload-tree handbook /docs --pages

Recover from a failure part way#

When a request fails, the operation stops and raises PloneOperationError. Its result lists what was already created, so you can remove it or carry on from there.

from plone_client import PloneOperationError

try:
    operations.upload_tree(client, "handbook", "/docs")
except PloneOperationError as error:
    for entry in error.result.created:
        print("created before the failure:", entry["@id"])
    raise

On the command line, the error on standard error has the same result.