Content population layer for SilverStripe 5.2+, built on
colymba/silverstripe-restfulapi
(via dynamic/silverstripe-restfulapi, a
maintained SS5-compatible fork — see Requirements below). The dependency provides token authentication
and generic REST CRUD at /api; this module adds everything programmatic content
population needs on top: atomic page compositions, batch operations with per-operation
results, asset ingestion, stage-aware reads and publish actions, schema introspection,
and field-level write guarding for the generic surface.
One token, one models map, two cooperating surfaces:
| Surface | Route | Provides |
|---|---|---|
| colymba RESTfulAPI | /api |
GET/POST/PUT/DELETE api/$Model(/$ID), api/auth/login|logout, token auth |
| this module | /content-api/v1 |
stage-aware GET (_stage, ext:), publish/unpublish/archive actions, batch, compositions, assets, pages, schema, GET auth/session |
Every endpoint maps 1:1 onto an MCP tool (schema/endpoints.json holds the tool
definitions — see the companion silverstripe-content-api-mcp
server). The module is the API-driven successor to Populate-YAML-fixture workflows —
see docs/en/13_migrating-from-fixtures.md.
- SilverStripe ^5.2, PHP ^8.1
colymba/silverstripe-restfulapi^5.0— resolves to dynamic/silverstripe-restfulapi (see Installation below), a fork of silverstripeltd'sfeature/v5branch fixing 4 calls to methods removed in SilverStripe 4+; see docs/en/upstream-issues.md
This is the
1branch (SilverStripe 5.2 line, default). Branch2targets SilverStripe 6 and requirescolymba/silverstripe-restfulapi'sfeature/cms-6-compatibilitybranch instead.
Optional integrations (feature-gated at runtime; endpoints answer 501 FEATURE_UNAVAILABLE when absent): dnadesign/silverstripe-elemental (compositions),
silverstripe/linkfield (link payloads), dynamic/silverstripe-essentials-tools
($palette()/$button() color tokens), dynamic/silverstripe-elemental-templates
(apply-template).
The colymba dependency is consumed from dynamic/silverstripe-restfulapi's tagged 5.0.0 release
(a package name unchanged from upstream, so it satisfies the same colymba/silverstripe-restfulapi
constraint everywhere), so your project root composer.json must add the VCS entry (composer
ignores a dependency's own repositories):
"repositories": [
{ "type": "vcs", "url": "https://github.com/dynamic/silverstripe-restfulapi" }
]composer require colymba/silverstripe-restfulapi:^5.0
composer require dynamic/silverstripe-content-api
dynamic/silverstripe-restfulapiis Dynamic's maintained fork of silverstripeltd'sfeature/v5branch, fixing 4 calls to methods removed in SilverStripe 4+ (Member::login()/logout(),DataObject::stat()). See docs/en/upstream-issues.md for background.^5.0resolves to a real tag, not a dev branch, so nominimum-stabilityworkaround is needed for this dependency specifically.
Upgrading from an earlier version? See docs/en/00_installation.md for the 1.0.x and 1.1.x migration notes.
- Expose classes — one map drives both surfaces (deny-by-default; a class must be
mapped AND granted
api_access):
Colymba\RESTfulAPI\QueryHandlers\DefaultQueryHandler:
models:
BlockPage: Dynamic\Base\Page\BlockPage
ElementContent: DNADesign\Elemental\Models\ElementContent
Image: SilverStripe\Assets\Image
# content-api-only refs (or overrides) go on the module's registry:
Dynamic\ContentApi\Registry\ClassRegistry:
models:
ElementalArea: DNADesign\Elemental\Models\ElementalArea
DNADesign\Elemental\Models\ElementContent:
api_access: 'GET,POST,PUT' # colymba HTTP verbs; the module maps them to
# read/create/update (plus: delete, action)
api_writable_fields: [Title, HTML, Sort, ShowTitle]
extensions:
- Dynamic\ContentApi\Write\WriteGuardExtension- Apply extensions — the external-id extension to classes the API should upsert, and
ContentApiGrantExtensionto any class a service account needs to write without holdingADMIN:
SilverStripe\CMS\Model\SiteTree:
extensions:
- Dynamic\ContentApi\Identity\ExternalIdentifierExtension
- Dynamic\ContentApi\Security\ContentApiGrantExtension
DNADesign\Elemental\Models\BaseElement:
extensions:
- Dynamic\ContentApi\Identity\ExternalIdentifierExtension
- Dynamic\ContentApi\Security\ContentApiGrantExtension
SilverStripe\Assets\File:
extensions: ['Dynamic\ContentApi\Identity\ExternalIdentifierExtension']- Grant permissions and mint a token:
sake dev/tasks/MintContentApiToken email=agent@example.com- Call it:
curl -H "X-Silverstripe-Apitoken: $TOKEN" https://site.test/content-api/v1/schema/site
curl -H "X-Silverstripe-Apitoken: $TOKEN" https://site.test/api/ElementContentFull walkthrough with permission codes and next steps: docs/en/01_quickstart.md.
SECURITY: never grant write verbs (
POST,PUT) inapi_accesswithoutWriteGuardExtension— colymba's write path natively applies every key in the payload with no writability check at all. See docs/en/04_security-model.md for the full class/record/field ACL model, write policies, and why the two write surfaces fail differently (revert-and-200 vs. reject-the-payload).
This repo carries two parallel lines: 1 (default, this branch, SilverStripe 5.2) and 2
(SilverStripe 6). 1 receives changes only via git merge origin/2 — never the reverse,
and never a cherry-pick (which would leave no merge base and re-present the same commits as
conflicts on the next sync). (This merge direction is itself scheduled to flip — see
issue #106 — once branch 2
has its own doc-drift script; until then, 2 remains the source branch merges flow from.) The
two branches are allowed to differ only in:
- composer constraints (framework/cms/PHP versions, the colymba branch + patch)
- task entry-point signatures (
1'ssrc/Tasks/*.phpuse SS5's legacyBuildTask::run($request);2uses SS6'sexecute(InputInterface, PolyOutput): int— each file carries a docblock noting the other branch's copy must be hand-ported. The branch-neutral business logic behind both lives inTasks/Support/, kept behaviorally identical across branches —TaskResult/TaskStatusare byte-identical;ApiTokenMinter/ServiceAccountProvisioner's class docblocks may legitimately differ in wording where they describe each branch's own adapter, but their code and returned messages must not) - requirement statements in this README and
docs/en/00_installation.md - PHP-floor-dependent type declarations (e.g.
Security/ContentApiGrantExtension.php's?boolhere vstrue|nullon branch2— standalonetrueas a return type needs PHP 8.2+, and this branch's floor is^8.1) - SS6-only glue with no
1equivalent (e.g.Tasks/Support/TaskResultRenderer.php, which wraps Symfony Console'sPolyOutput/Command— not present on this branch at all, since its two adaptersechoTaskResult::$linesdirectly instead) - one documented
silverstripe/versionedbehavioral divergence: branch2'scanDelete()is vetoed by an unmetcanUnpublish()on an already-published record; this branch'ssilverstripe/versioned2.4.x-devhas no such veto (seeContentApiGrantExtension's class docblock anddocs/en/04_security-model.md#grant-extension)
Everything else — application logic, tests, docs content beyond the requirements
blocks — should read identically on both branches after a sync. A CI check
(.local-ci.json's doc-drift entry) greps this branch for SS6-only requirement text and the
SS6 tasks: invocation namespace to catch regressions.
Full reference lives in docs/en/:
| Page | Covers |
|---|---|
| Installation | Requirements, install, upgrade notes |
| Quick start | Expose a class, mint a token, first calls |
| Configuration reference | Every config option, every class, with defaults |
| Authentication | Token model, minting, hardened auth, colymba caveats |
| Security model | Permission codes, ACL, write policies, trusted fields channel |
| Endpoint reference | Every route, method, params |
| Write payloads | fields/relations shape, polymorphic has_one, color tokens, links |
| Batch operations | Ordered ops, atomic rollback, delete modes |
| Page compositions | Atomic full-page writes |
| Assets | Upload/read, conflict modes |
| Publishing & stages | Draft/live, _stage, publish modes |
| Schema introspection | GET schema family |
| Error codes | The full machine-readable error reference |
| Migrating from fixtures | Populate-YAML → content API |
| Architecture | Request lifecycle, service map, the two write surfaces |
| Testing & contributing | Test setup, running the suite, spec-sync |
| Upstream issues | The colymba/silverstripe-restfulapi support workstream |
schema/endpoints.json describes this module's endpoints as MCP-style tool definitions;
GET /schema/$ClassRef supplies per-class payload contracts at runtime.
BSD-3-Clause. See LICENSE.md.