Skip to content

CLI Commands Since 3.5.0

The tcms CLI tool is the command-line interface to Total CMS. It exposes core CMS services as composable terminal commands, designed for both AI coding agents and human developers.

Terminal window
php resources/bin/tcms <command> [options] [arguments]

All commands support a --json flag that outputs valid JSON to stdout. This is the contract AI agents rely on — no decorative output, no progress bars, no color codes.

OptionDescription
--jsonOutput as JSON (for AI agent compatibility)
-vVerbose output
-qQuiet mode (errors only)
-h, --helpDisplay help for a command
-V, --versionDisplay version

Show site status, version, edition, license, collection count, and cache backend.

Terminal window
tcms info
tcms info --json

JSON output:

{
"version": "3.2.2",
"build": "7f080a63",
"edition": "pro",
"license": { "valid": true, "trial": false, "trialDaysRemaining": null },
"domain": "example.com",
"collections": { "total": 12 },
"schemas": { "reserved": 22, "custom": 4 },
"cache": { "backend": "apcu" }
}

List all schemas.

Terminal window
tcms schema:list
tcms schema:list --custom
tcms schema:list --reserved
tcms schema:list --category=Commerce
tcms schema:list --json
OptionDescription
--customOnly show custom schemas
--reservedOnly show reserved (built-in) schemas
--categoryFilter by category

Show full schema definition including properties, types, and field configurations.

Terminal window
tcms schema:get blog
tcms schema:get blog --json
ArgumentRequiredDescription
idYesSchema ID

Export a schema to a JSON file.

Terminal window
tcms schema:export blog --output=blog-schema.json
tcms schema:export blog
ArgumentRequiredDescription
idYesSchema ID
OptionDescription
--output, -oOutput file path (omit for stdout)

Import a schema from a JSON file. Creates or updates the schema.

Terminal window
tcms schema:import my-schema.json
tcms schema:import my-schema.json --json
ArgumentRequiredDescription
fileYesPath to schema JSON file

Lint stored schemas without saving anything. schema:import validates on the way in, but schemas edited in place (by hand or by an AI agent) are never re-validated — the linter closes that gap. Errors are structural problems that break at runtime: no id property defined, required/index entries naming undefined properties, inheritFrom pointing at a missing schema, deck/card schemaref targets that don’t exist or contain deck-incompatible types, and meta-schema violations. Warnings flag agent-legibility gaps — properties without help text (help feeds the MCP tool catalog) and schemas without a description.

Terminal window
tcms schema:lint # all custom schemas
tcms schema:lint blog # one schema (reserved schemas allowed by ID)
tcms schema:lint --strict # warnings also fail the run
tcms schema:lint --json # machine-readable report
ArgumentRequiredDescription
idNoSchema ID (default: lint all custom schemas)
OptionDescription
--strictTreat warnings as failures

Exit code is 0 when no errors were found (warnings allowed), 1 when errors were found — or warnings under --strict — so it slots into CI.


List all collections with their schema and object count.

Terminal window
tcms collection:list
tcms collection:list --schema=blog
tcms collection:list --category=Content
tcms collection:list --json
OptionDescription
--schemaFilter by schema type
--categoryFilter by category

Show collection metadata including schema, sort order, and object count.

Terminal window
tcms collection:get blog
tcms collection:get blog --json
ArgumentRequiredDescription
idYesCollection ID

Query a collection’s index with filtering, searching, sorting, and pagination.

Terminal window
tcms collection:query posts --search="photography" --limit=5
tcms collection:query posts --include="featured:true" --sort="-date"
tcms collection:query posts --exclude="draft:true" --limit=10 --offset=20
tcms collection:query posts --filter="title:drone" --json
ArgumentRequiredDescription
idYesCollection ID
OptionDescription
--search, -sFull-text search query
--filterContains filter (field:value)
--includeInclude filter (field:value,field:value)
--excludeExclude filter (field:value,field:value)
--sortSort by property (prefix with - for descending)
--limit, -lMaximum results (default: 20)
--offset, -oNumber of results to skip (default: 0)

JSON output:

{
"total": 37,
"offset": 0,
"limit": 5,
"results": [...]
}

Export a collection to JSON, CSV, or ZIP.

Terminal window
tcms collection:export blog --output=blog.json
tcms collection:export blog --format=csv --output=blog.csv
tcms collection:export blog --format=zip --output=blog-backup.zip
ArgumentRequiredDescription
idYesCollection ID
OptionDescription
--format, -fExport format: json, csv, or zip (default: json)
--output, -oOutput file path (omit for stdout; zip generates a default filename)

The zip format includes all object JSON files and their associated media/assets. JSON export uses streaming for large collections when --output is specified.

Import objects into a collection from a JSON or CSV file.

Terminal window
tcms collection:import blog posts.json
tcms collection:import blog data.csv
tcms collection:import blog posts.json --update
tcms collection:import blog posts.json --format=json --json
ArgumentRequiredDescription
idYesCollection ID
fileYesPath to JSON or CSV file
OptionDescription
--format, -fImport format: json or csv (auto-detected from extension)
--updateUpdate existing objects instead of skipping

List object IDs in a collection.

Terminal window
tcms object:list blog
tcms object:list blog --limit=10 --offset=20
tcms object:list blog --json
ArgumentRequiredDescription
collectionYesCollection ID
OptionDescription
--limit, -lMaximum results
--offset, -oNumber of results to skip

Fetch a single object with all its properties.

Terminal window
tcms object:get blog my-post
tcms object:get blog my-post --json
ArgumentRequiredDescription
collectionYesCollection ID
idYesObject ID

Create one object from a JSON file (or stdin with -). The object goes through the full save pipeline — schema defaults fill in, validation runs, the index updates, and the object.created event fires — exactly as if it were saved from the admin. Refuses to overwrite an existing object, and refuses a JSON array (use collection:import for bulk data).

Terminal window
tcms object:create blog my-post.json
echo '{"id":"hello","title":"Hello"}' | tcms object:create blog -
tcms object:create blog my-post.json --json # echoes the saved object
ArgumentRequiredDescription
collectionYesCollection ID
fileYesPath to a JSON file holding one object, or - for stdin

Merge changes into an existing object, leaving every field you don’t mention untouched. Like object:create, it goes through the full save pipeline — validation runs, the collection index updates, and object.updated fires — so it stays consistent with the admin in a way that editing the JSON file by hand does not.

Use this for a targeted change to one object. object:create only makes new objects, and collection:import works in bulk.

Terminal window
tcms object:patch blog my-post patch.json
echo '{"title":"New Title"}' | tcms object:patch blog my-post -
tcms object:patch blog my-post patch.json --json # echoes the saved object

The merge is shallow. Patching a structured field from the top level replaces it wholesale, which will quietly discard the parts you didn’t mention. Target the property instead to merge into it:

Terminal window
# Replaces the whole image field — name, size and dimensions are lost
echo '{"image":{"alt":"A description"}}' | tcms object:patch image social -
# Changes only alt, preserving everything else on the field
echo '{"alt":"A description"}' | tcms object:patch image social - --property=image
ArgumentRequiredDescription
collectionYesCollection ID
idYesObject ID
fileYesPath to a JSON file holding the fields to merge, or - for stdin
OptionDescription
--propertyMerge into this property instead of the top level, preserving its other keys

Export a single object as JSON or ZIP (with assets).

Terminal window
tcms object:export blog my-post --output=my-post.json
tcms object:export blog my-post --format=zip --output=my-post.zip
tcms object:export blog my-post
ArgumentRequiredDescription
collectionYesCollection ID
idYesObject ID
OptionDescription
--format, -fExport format: json or zip (default: json)
--output, -oOutput file path (omit for stdout; zip generates a default filename)

Delete a single object. The deletion goes through the same index-aware path as the admin UI, so the collection’s .index.json and object count stay consistent — unlike removing the flat file by hand, which leaves them stale.

Terminal window
tcms object:delete blog my-post
tcms object:delete blog my-post --force
ArgumentRequiredDescription
collectionYesCollection ID
idYesObject ID
OptionDescription
--force, -fSkip the confirmation prompt (required for non-interactive/CI use)

Import items into a deck property from a JSON or CSV file.

Terminal window
tcms deck:import invoices inv-001 items line-items.json
tcms deck:import invoices inv-001 items line-items.csv --update
ArgumentRequiredDescription
collectionYesCollection ID
objectYesObject ID
propertyYesDeck property name
fileYesPath to JSON or CSV file
OptionDescription
--format, -fImport format: json or csv (auto-detected from extension)
--updateUpdate existing deck items instead of skipping

Queue every entry from an RSS, Atom, or JSON feed into a target collection. The CLI counterpart to the Utilities → Import RSS admin page. Designed for cron — the admin form has a “Schedule with cron” panel that builds the exact command line for the configured import so operators can paste it directly into crontab.

Terminal window
# Basic import — auto field mapping, items queued as drafts
tcms rss:import https://example.com/feed.xml blog
# Publish immediately
tcms rss:import https://example.com/feed.xml blog --no-draft
# Drain the queue in the same cron run
tcms rss:import https://example.com/feed.xml blog && tcms jobs:process
ArgumentRequiredDescription
urlYesRSS / Atom / JSON Feed URL
collectionYesTarget collection ID
OptionDescription
--draft / --no-draftQueue items as drafts (default) or publish immediately
--map, -mField mapping in the form feedField=collectionField. Repeat the option or comma-separate within one value. See below.
--user-agentUser-Agent for the feed request. See below.
--jsonOutput JSON (success status + count)

Feed requests identify themselves as TotalCMS/{version} (+https://totalcms.co). Some hosts — Cloudflare-fronted sites in particular — block requests from unrecognized or generic clients outright, and a feed that opens fine in a browser can still return 403 to a server.

If a host rejects the default, --user-agent sets your own:

Terminal window
tcms rss:import https://example.com/feed.xml blog \
--user-agent "AcmeNews/1.0 (+https://acme.example)"

Identify yourself honestly — a contactable URL is what gets a well-run site to allowlist you. Impersonating a browser tends to be treated as evasion and blocked harder.

The importer maps these eight feed-side fields to your collection’s properties:

Feed fieldDefault collection property
titletitle
contentcontent
summarysummary
datedate
authorauthor
categoriescategories
linkmedia
imageimage

To override a default mapping, pass --map feedField=collectionProperty. To drop a field entirely (don’t write it to the object), map it to an empty string with --map feedField=.

Terminal window
# Map the feed's `title` into your schema's `heading` property,
# and the feed's `content` into your schema's `body` property.
tcms rss:import https://example.com/feed.xml blog \
--map title=heading \
--map content=body
# Same thing, comma-separated single argument (handy in crontab one-liners).
tcms rss:import https://example.com/feed.xml blog --map "title=heading,content=body"
# Drop the link/categories fields entirely; remap the rest.
tcms rss:import https://example.com/feed.xml news \
--map title=headline \
--map content=body \
--map image=hero \
--map link= \
--map categories=
# Realistic news-import recipe targeting a schema with
# `heading`, `body`, `excerpt`, `published`, `byline`, `tags`, `hero`.
tcms rss:import https://example.com/feed.xml news --no-draft \
--map title=heading \
--map content=body \
--map summary=excerpt \
--map date=published \
--map author=byline \
--map categories=tags \
--map image=hero
# Hourly RSS pull — drain the queue right after queuing
0 * * * * /usr/local/bin/php /var/www/site/resources/bin/tcms rss:import "https://example.com/feed.xml" blog --no-draft --map "title=heading,content=body" && /usr/local/bin/php /var/www/site/resources/bin/tcms jobs:process

The admin UI’s “Schedule with cron” panel under Utilities → Import RSS prints this command for you with your site’s PHP and install paths already filled in — open the panel, copy, paste into crontab.


Export all site data (schemas, collections, objects, templates) as a JumpStart file.

Terminal window
tcms jumpstart:export --output=my-site.json
tcms jumpstart:export --name="My Site" --description="Full site export"
tcms jumpstart:export --json
OptionDescription
--nameName for the export
--descriptionDescription for the export
--output, -oOutput file path (generates default filename if omitted)

Import a JumpStart file to set up schemas, collections, objects, and templates.

Terminal window
tcms jumpstart:import my-site.json
tcms jumpstart:import my-site.json --json
ArgumentRequiredDescription
fileYesPath to JumpStart JSON file

Push and pull schemas, templates, site-machinery objects, and collection settings between a local development instance and a production server. Configure the production server URL and API key in Settings > Sync in the admin dashboard. Full details, including the object-seeding workflow: Sync guide.

3.5.1 note: --collections now means collection settings (it used to mean objects from five allowlisted collections). The flag formerly named collection-meta is gone — --collections does its job now. Objects for those five collections move via their own feature flags instead (--pages, --dataviews, --mailer, --mcp-prompts, --automations), and any other collection’s object data can be seeded with --objects. Full breaking-changes writeup: Sync guide.

Push schemas, templates, site-machinery objects, collection settings, and (optionally) seed object data to the production server.

Terminal window
tcms push
tcms push --dry-run
tcms push --schemas=blog,products
tcms push --templates=blog-post,sidebar
tcms push --pages
tcms push --pages=home,about
tcms push --collections=comparisons,builder-pages
tcms push --objects=blog
tcms push --objects=blog:launch-day --overwrite --dry-run
tcms push --schemas=blog --templates=blog-post --dry-run
OptionDescription
--schemasComma-separated schema IDs to push
--templatesComma-separated template IDs to push
--pages[=id,id]Site Builder pages — all of them, or just the ones listed
--dataviews[=id,id]Data Views — all of them, or just the ones listed
--mailer[=id,id]Mailer templates — all of them, or just the ones listed
--mcp-prompts[=id,id]MCP prompts — all of them, or just the ones listed
--automations[=id,id]Automations — all of them, or just the ones listed
--collectionsComma-separated collection IDs whose SETTINGS to push (any collection; counters never travel)
--objectsSeed object data: collection or collection:id,id, repeatable. Existing objects on the target are skipped unless --overwrite is given. Push-only
--overwriteLet --objects overwrite objects that already exist on the target
--forceAllow --overwrite without a prior --dry-run when not attached to a terminal
--dry-runCompare both sides: per-item unchanged/differs/new status with newer-side hints

Pull schemas, templates, site-machinery objects, and collection settings from the production server. --objects/--overwrite have no pull equivalent — seeding is push-only.

Terminal window
tcms pull
tcms pull --dry-run
tcms pull --schemas=blog
tcms pull --templates=blog-post,sidebar
tcms pull --pages=home,about
tcms pull --collections=comparisons
OptionDescription
--schemasComma-separated schema IDs to pull
--templatesComma-separated template IDs to pull
--pages[=id,id]Site Builder pages — all of them, or just the ones listed
--dataviews[=id,id]Data Views — all of them, or just the ones listed
--mailer[=id,id]Mailer templates — all of them, or just the ones listed
--mcp-prompts[=id,id]MCP prompts — all of them, or just the ones listed
--automations[=id,id]Automations — all of them, or just the ones listed
--collectionsComma-separated collection IDs whose SETTINGS to pull (any collection; counters never travel)
--dry-runCompare both sides: per-item unchanged/differs/new status with newer-side hints

What gets synced: Custom schemas, custom templates, collection settings for any collection (never its counters), objects for the five feature-flagged collections — builder-pages (--pages), mailer, mcp-prompt (--mcp-prompts), dataviews, automations — and, push-only, seeded object data for any other seedable collection via --objects.

What never gets synced: Objects in custom collections you haven’t named with --objects; the image, gallery, file, depot, and playground collections, which can never be seeded; image/file/gallery/depot fields on any object, which are always stripped from every payload; media/images; system settings; API keys; reserved schemas. A custom collection’s schema syncs; its objects only travel if you explicitly seed them.

Filter semantics: a bare tcms push or tcms pull is a full mirror of schemas, templates, the five feature-flagged collections, and collection settings — it never seeds --objects, which only runs when named explicitly. The moment any filter flag is given, the categories you did not mention are excluded entirely, so tcms push --schemas=blog moves the blog schema and nothing else.

Backups: before an overwrite lands, the receiving instance snapshots the current version to tcms-data/.system/backups/{schemas,objects}/... (ten most recent per item). See the Sync guide for details.


Clear all caches.

Terminal window
tcms cache:clear
tcms cache:clear --json

How this reaches the web server. APCu is per-process, so a CLI run cannot clear the cache the web server is holding — the two never share memory. Instead, cache:clear clears what it can reach directly (filesystem, Redis, Memcached) and writes a signal file to tcms-data/.system/.cache_invalidate. The next request to hit the site replays that signal and clears APCu in the web process.

That means the clear does not take effect until someone loads a page. If you clear from a deploy script and immediately check the site, the very request you make is the one that applies it — so a single reload can still look stale. Load the page twice.

If a clear appears not to have worked, check whether the signal file is still sitting there:

Terminal window
ls tcms-data/.system/.cache_invalidate

Present means no request has replayed it yet. Gone means it was applied.

Alternatives when you are iterating. Two options avoid the round trip entirely:

  • Turn on Developer Mode in the admin. Cache reads are bypassed outright while it is active, so you see fresh output on every request without clearing anything. This is the right choice while you are actively editing templates or content.
  • Hit the HTTP endpoint at /api/emergency/cache/clear, which clears in the web process directly rather than by signal. It needs no login, and it is rate-limited to one call per IP every 15 minutes.

Note the /api prefix — /emergency/cache/clear without it returns a 404.

Process the pending job queue. This is typically run via cron.

Terminal window
tcms jobs:process
tcms jobs:process -v
tcms jobs:process --memory=1G
tcms jobs:process --json
OptionDescription
--memory, -mMemory limit (default: 512M)
-vVerbose output with per-job details

Cron setup:

Terminal window
* * * * * php /path/to/resources/bin/tcms jobs:process

Fire due scheduled automations. Runs on its own cron line, parallel to jobs:process, so a large import backlog in the job queue never delays a time-sensitive scheduled automation. Single-flight locked, so overlapping cron ticks can’t double-fire the same run.

Terminal window
tcms automations:process
tcms automations:process --json

Webhook and event triggers do not depend on this command — webhooks fire on HTTP request and events fire when the originating write happens; their async runs are drained on the next tick.

Cron setup (add this as a second line, alongside jobs:process):

Terminal window
* * * * * php /path/to/resources/bin/tcms automations:process

See the MCP Server guide for the full server documentation.

Show the MCP server’s operator-facing health: enabled state, public-access switch, edition gate, tool prefix, and the tool list each persona sees. Saved-query tools defined in collection MCP cards are included and annotated (saved query).

Terminal window
tcms mcp:status
tcms mcp:status --json # persona tool lists plus a schema_tools array

Invoke a tool locally without going through the HTTP endpoint — useful for verifying a tool’s output and persona visibility before an agent connects.

Terminal window
tcms mcp:test query_collection --params='{"collection":"blog","limit":3}'
tcms mcp:test query_collection --params='{"collection":"blog"}' --persona=public

Rebuild a collection’s .index.json and totalObjects count from the objects on disk. Use this when the index has drifted from the actual files — for example after an object’s flat file was added or removed out-of-band. (search:reindex only touches the search provider; repair:files only rebuilds file/image metadata.)

Terminal window
tcms repair:index blog
tcms repair:index --all
ArgumentRequiredDescription
collectionNoCollection ID (omit and use --all to rebuild every collection)
OptionDescription
--allRebuild the index for every collection

Check for available updates from the license server.

Terminal window
tcms update:check
tcms update:check --json

JSON output:

{
"current": "3.2.2",
"available": true,
"version": "3.5.0",
"releaseDate": "2026-04-10",
"severity": "minor",
"changelog": "New features and improvements",
"downloadUrl": "/version/download/3.5.0"
}

Download and apply an available update. The site enters maintenance mode during the swap.

Terminal window
tcms update:apply
tcms update:apply --force
tcms update:apply --json
OptionDescription
--forceSkip confirmation prompt

The previous version is backed up automatically for rollback.

Roll back to the previous version after a failed or unwanted update.

Terminal window
tcms update:rollback
tcms update:rollback --force
OptionDescription
--forceSkip confirmation prompt

Restores the backup directory created during the most recent update.


List all discovered extensions with their status.

Terminal window
tcms extension:list
tcms extension:list --json

JSON output:

[
{
"id": "acme/seo-pro",
"name": "SEO Pro",
"version": "1.2.0",
"enabled": true,
"error": null
}
]

Enable a discovered extension.

Terminal window
tcms extension:enable acme/seo-pro
tcms extension:enable acme/seo-pro --json
ArgumentRequiredDescription
idYesExtension ID (e.g. vendor/extension-name)

Disable an extension without removing it.

Terminal window
tcms extension:disable acme/seo-pro
tcms extension:disable acme/seo-pro --json
ArgumentRequiredDescription
idYesExtension ID (e.g. vendor/extension-name)

Remove an extension’s files. Extension data in tcms-data is preserved.

Terminal window
tcms extension:remove acme/seo-pro
tcms extension:remove acme/seo-pro --force
ArgumentRequiredDescription
idYesExtension ID (e.g. vendor/extension-name)
OptionDescription
--force, -fSkip confirmation prompt