protonsystems / demo-content
Reusable deterministic demo-content generation for paragraph-heavy Drupal projects.
Package info
gitlab.com/Proton.Systems/drupal/demo-content
Type:drupal-module
pkg:composer/protonsystems/demo-content
Requires
- php: ^8.3
- drupal/core: ^10 || ^11
- drupal/paragraphs: ^1.20
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Reusable Drupal module for generating deterministic demo content on paragraph-heavy sites.
Purpose
The module is intended to be reused across Drupal 10 and Drupal 11 projects by installing it as a Composer package and enabling it in the target site.
What it does
- creates a deterministic node for a chosen content type;
- discovers the main paragraph field automatically;
- creates one instance of every allowed top-level paragraph bundle;
- recurses into nested paragraph fields;
- creates or reuses referenced media, nodes, terms, and other entities;
- skips problematic computed/internal fields such as node
menu_link; - creates the site's standard boilerplate pages (Home, Privacy Policy, Legal Notice, Contact) with real placeholder copy, a recommended URL alias, and Home set as the front page;
- tracks every entity it generates so it can all be deleted again with one command.
Requirements
- PHP 8.3+
- Drupal 10 or 11
- Paragraphs
- Drush 12+
Commands
drush demo-content:coverage-node <bundle> (alias: demo:coverage-node)
Creates one coverage node for the requested node bundle. The command requires a paragraph field on that bundle, either auto-detected or supplied explicitly. It creates one top-level instance of every allowed paragraph bundle and then recurses into nested paragraph/reference fields.
drush demo-content:coverage-node page
drush demo-content:coverage-node page --replace
Options:
--title=<text>: use a custom node title instead of the generated default--paragraph-field=<machine_name>: override paragraph field auto-detection--nested-repeat=<n>: repeat single-bundle nested paragraph/reference lists; defaults to2--replace: delete an existing node with the same bundle/title before creating the new coverage node
Examples:
drush demo-content:coverage-node page --title="Paragraph coverage page"
drush demo-content:coverage-node page --paragraph-field=field_paragraphs
drush demo-content:coverage-node page --nested-repeat=3
drush demo-content:coverage-node page --replace
drush demo-content:coverage-all (alias: demo:coverage-all)
Creates one demo node for every requested node bundle. If --bundles is not
provided, the module runs across all available node bundles in stable sorted
order. Bundles without a paragraph field are still created; those nodes are
logged as created without paragraph coverage.
drush demo-content:coverage-all
drush demo-content:coverage-all --bundles=page,project --replace
Options:
--bundles=<bundle1,bundle2>: comma-separated node bundles; defaults to all node bundles--nested-repeat=<n>: repeat single-bundle nested paragraph/reference lists; defaults to2--replace: delete an existing generated node with the same bundle/title before recreating it
Examples:
drush demo-content:coverage-all --bundles=page,project
drush demo-content:coverage-all --bundles=page,project --nested-repeat=3 --replace
drush demo-content:create-standard-pages (alias: demo:standard-pages)
Creates the site's standard boilerplate pages — Home, Privacy Policy, Legal
Notice, Contact — as page nodes (or another bundle via --bundle), each
with real placeholder copy (not synthetic coverage data), a recommended URL
alias, and Home set as the site's front page.
drush demo-content:create-standard-pages
drush demo-content:create-standard-pages --pages=privacy_policy,legal_notice
drush demo-content:create-standard-pages --replace
Recommended/default URLs: /home (also set as front page), /privacy-policy,
/legal-notice, /contact.
Options:
--bundle=<machine_name>: node bundle to use; defaults topage--pages=<id1,id2>: comma-separated subset ofhome,privacy_policy,legal_notice,contact; defaults to all four--replace: delete an existing page with the same title before creating it--no-front: don't set Home as the site front page
Node IDs are never forced — Drupal assigns them automatically. On a fresh install with an empty node table, Home (created first) naturally becomes node 1.
Language: pages are currently created in English only. A --langcode
option (so a German-default site can get Impressum instead of Legal
Notice, for example) is planned but not yet implemented — see
STANDARD_PAGES in DemoContentGenerator for the extension point.
drush demo-content:purge (alias: demo:purge)
Deletes every entity this module has generated — across every past command
invocation, not just the most recent one. This covers top-level nodes,
nested paragraphs, and any referenced media/taxonomy/nodes/other entities
created along the way, plus any path aliases created by
demo-content:create-standard-pages.
drush demo-content:purge
drush demo-content:purge --dry-run
Options:
--dry-run: report what would be deleted without deleting anything--no-legacy-scan: skip the title-pattern sweep described below
Generated entities are tracked in Drupal State as they're created, so purge
doesn't need to guess what's demo content — with one exception: content
created by a module version before this tracking existed has no State
record. To handle that, purge also sweeps for nodes titled with the
Demo coverage: prefix that demo-content:coverage-node/coverage-all use
by default (not the paragraphs/media/other entities those runs also created,
since only that node title prefix is distinctive enough to be a safe,
generic content signal). A node created with a custom --title bypasses
this sweep; going forward, any run of this module tracks entities exactly,
regardless of title.
Installation
composer require protonsystems/demo-content
drush en demo_content
Extension points
The generic engine lives in a service, while project-specific customizations
can be added via alter and presave hooks documented in demo_content.api.php.
Releasing A New Version On Packagist
Packagist publishes versions of protonsystems/demo-content from git tags. Do
not add a version field to composer.json; create and push a new release tag
instead.
For the first public release, submit the public Git repository URL to
Packagist. If you want automatic updates from GitLab, configure the Packagist
integration in GitLab under Settings > Integrations using your Packagist
username and API token.
Prepare the release commit on main:
git checkout main
git pull origin main
composer validate --no-check-publish --strict
git status
git add <files>
git commit -m "Release v1.0.0"
git push origin main
Create and push an annotated tag for the release:
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
Use semantic version tags such as v1.0.0. After the tag is pushed, Packagist
should detect the new version automatically. If it does not appear, open the
package page in Packagist and trigger a manual update.