Craft CMS 5 plugin that pulls ContentiQ content into published Craft entries — primarily via a live API sync, with a manual JSON file import as the fallback path.
Important
The whole plugin at a glance, one doc per subsystem, plus a walk through the life of a sync.
Contents: Requirements · Installation · Configuration · Usage · How it fits with ContentiQ · Documentation · Local development · Releasing · Related repositories
- Craft CMS 5.0+
- PHP 8.2+
composer require matrixcreate/contentiq-craft-import
php craft plugin/install contentiq-importerAdd config/contentiq.php to your Craft project:
return [
'section' => 'pages', // Entry section handle
'entryType' => 'pages', // Entry type handle
'assetVolume' => 'images', // Asset volume for imported images
'assetFolder' => 'contentiq', // Folder within the volume
'matrixField' => 'contentBlocks',
'seoField' => 'seo',
];All keys are optional — the values above are the defaults. See docs/integration.md for the full config surface, including blockOverrides, content_types, slugMap, and the diff-aware preserveBlockIdentity flag.
Add API credentials to .env:
CONTENTIQ_URL=https://your-contentiq-instance.com
CONTENTIQ_API_KEY=ciq_your-project_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
Then reference them in the plugin settings: CP → Settings → Plugins → ContentiQ.
The Sync screen in the CP loads the full ContentiQ sitemap (including pages never yet imported into Craft) and lets you select what to pull. Submitting runs via Craft's queue — the screen polls for completion and shows a hierarchical report when done. See the life of a sync for the full sequence and docs/cp-and-widget.md for a tour of the screen.
Each entry edit screen shows a ContentiQ sidebar widget with:
- Sync — pulls and re-imports just this entry from the API
- Lock — a lightswitch that stops this entry being overwritten by a sync (a never-synced entry is locked by default)
- Synced at — the last sync time, with a Reload link that appears after a fresh sync (the entry's fields aren't re-rendered live)
- Notes — developer notes attached to content blocks in ContentiQ
See docs/cp-and-widget.md for the full behaviour.
Navigate to ContentiQ in the Craft control panel. Upload a JSON export file, review the dry-run preview, then confirm to import. See docs/cp-and-widget.md for a tour of every screen.
php craft contentiq-importer/import --file=export.json
php craft contentiq-importer/import --file=export.json --dry-run
php craft contentiq-importer/import --file=export.json --verbose
php craft contentiq-importer/import --file=export.json --forceimport is the controller's only (and default) action. It detects single-page vs. batch export format automatically from the JSON shape (a top-level blocks array is a single page, a top-level pages array is a batch) and loops the same pipeline either way.
--file(-f) — path to the ContentiQ JSON export file. Required.--dry-run(-n) — validates and reports without writing anything or downloading assets.--verbose(-v) — logs each block and image as it's processed.--force— bypasses the entry-lock check. By default a locked entry is skipped (a missing lock row also counts as locked);--forceoverrides that so the CLI can write over it.
This plugin is a pull-only consumer of ContentiQ's export API — Craft never receives a push, and every sync is initiated from this side (a CP button, the CLI, or the sidebar widget). A sync overwrites an entry's content wholesale (whole-page replace), not a field-by-field merge; the only defence against a re-sync clobbering hand-edits is per-entry locking. After a successful write, this plugin sends an explicit acknowledgement back to ContentiQ (POST /api/v1/pages/ack) for the pages it actually wrote — that's the only thing that mutates ContentiQ's own state. See docs/integration.md for the full wire contract.
| Doc | Covers |
|---|---|
| integration.md | The four API endpoints, auth, the read-only-export/explicit-ack contract, and the config surface |
| import-pipeline.md | The per-page pipeline: find-or-create, the no-drafts save, collection children, homepage, hierarchy, locks |
| block-mapping.md | The declarative block mapping system, MatrixBuilder, NodesRenderer, hero, cards, CTA, preserveBlockIdentity |
| globals.md | Company info/offices/branding import, the per-run consent lock, and the URL-prefix drift check |
| assets.md | Image download and idempotency, SSRF/path-traversal guards, the CLI webroot requirement |
| cp-and-widget.md | Every CP screen, the sync report, and the entry sidebar widget |
Start at docs/README.md for the index and the life-of-a-sync narrative.
To develop the plugin and a Craft project simultaneously, run from the Craft project root:
# Switch to local symlinked copy
composer config repositories.contentiq '{"type":"path","url":"../contentiq-craft-import","options":{"symlink":true}}' \
&& composer require matrixcreate/contentiq-craft-import:@dev
# Revert to Packagist
git checkout composer.json composer.lock && composer installNever commit the path repository — the git checkout step ensures composer.json is clean before pushing. See AGENTS.md for the full workflow, including recovery if the path repo does get committed by accident.
Always pair a git tag with a GitHub release:
git tag 1.x.0
git push origin main --tags
gh release create 1.x.0 --title "1.x.0" --notes "- What changed"- contentiq — the Laravel app content is authored, reviewed, and approved in before this plugin ever sees it. Its
docs/delivery/covers the server side of the export/ack API contract this plugin consumes. - contentiq-payload (the
payload-syncpackage) — the Payload CMS equivalent of this plugin, consuming the same ContentiQ export API for Payload-based client sites. - craft-starter — the Craft site starter that provisioned client sites (the ones this plugin syncs into) are built from.