Umbraco.Cms.TestDataSeeder
A configurable performance test data seeder for Umbraco CMS v13 for performance and load testing. Automatically seeds languages, dictionary items, data types, document types, media, content, and users.
Install
| Product | Versions |
|---|---|
| .NET |
README
Umbraco.Cms.TestDataSeeder
A configurable performance / load-testing data seeder for Umbraco CMS. Automatically creates languages, dictionary items, data types, document types (including BlockGrid and BlockList compositions), media, content, users, and members on application startup.
β οΈ Not for production use. This package creates large volumes of content, media, users and members. It is intended for performance and load testing only. Install it in dedicated test environments.
Compatibility
The package major aligns with the supported Umbraco CMS major, matching
the rest of the Umbraco.Cms.* package family.
| Package version | Umbraco | .NET |
|---|---|---|
13.x |
Umbraco 13 | .NET 8 |
Support for Umbraco 17 ships on a separate release train (17.x).
Installation
dotnet add package Umbraco.Cms.TestDataSeeder --version 13.*
The package auto-registers via an IComposer, so no startup wiring is required in the host project. By default the seeder is disabled; enable it explicitly in appsettings.json (see below).
Configuration
Configuration lives under the Umbraco.Cms.TestDataSeeder root in appsettings.json, split into two sub-sections:
Umbraco.Cms.TestDataSeeder:Optionsβ runtime behaviour (enable/disable, preset, batching, publishing mode, domain suffix, etc.)Umbraco.Cms.TestDataSeeder:Configurationβ target counts per data type (languages, content, media, document types, β¦)
Minimal enable
{
"Umbraco.Cms.TestDataSeeder": {
"Options": {
"Enabled": true,
"Preset": "Medium"
}
}
}
With Preset set, the counts under Umbraco.Cms.TestDataSeeder:Configuration are overridden by the preset values. Use "Custom" (default) to drive counts from the Configuration section instead.
Presets
| Preset | Approximate total items | Use case |
|---|---|---|
Small |
~180 | Quick smoke tests |
Medium |
~1,400 | Moderate local testing |
Large |
~16,000 | Performance testing |
Massive |
~74,000 | Stress testing |
Custom |
driven by SeederConfiguration |
Full manual control |
Default data quantities (Preset = Custom, no overrides)
Reference numbers for what the seeder produces when nothing is overridden. Each row maps to the property of the same name in Umbraco.Cms.TestDataSeeder:Configuration; the totals are what the corresponding Total* getters on those configs return.
| Category | Default count | Source |
|---|---|---|
| Languages | 20 | Languages.Count |
| Dictionary items | 1,500 | Dictionary.TotalItems (10 Γ 5 Γ 30) |
| Data types (base) | 120 | DataTypes.Total (ListView 30 + MultiNodeTreePicker 40 + RichTextEditor 10 + MediaPicker 10 + Textarea 10 + Dropdown 10 + Numeric 10) |
| Block List / Block Grid data types | 100 | DocumentTypes.BlockList (40) + DocumentTypes.BlockGrid (60) |
| Element types | 130 | DocumentTypes.ElementTypes.Total (Simple 65 + Medium 39 + Complex 26) |
| Document types | 150 + 6 detail | DocumentTypes.TotalDocTypes (Variant 110 + Invariant 40), plus 6 fixed Detail doc types (Simple/Medium/Complex Γ Variant/Invariant) |
| Templates | 150 | DocumentTypes.TotalTemplates |
| Media items | 610 | Media.TotalCount (PDF 200 + PNG 200 + JPG 200 + Video 10) |
| Content nodes | 300 | Content.TotalTarget |
| Users | 30 | Users.Count |
| Members | 30 | Members.Count |
| Frontend pages | 3 (member login, member area, contact form) | created by MemberSeeder + ContactFormSeeder |
Presets scale these counts up or down; see the preset summary table above for approximate totals at each tier.
Runtime options (Umbraco.Cms.TestDataSeeder:Options)
| Key | Default | Purpose |
|---|---|---|
Enabled |
false |
Master switch. Must be set to true for seeding to run. |
Preset |
Custom |
Small / Medium / Large / Massive / Custom. |
StopOnError |
false |
Abort the whole run on the first seeder failure. |
DryRun |
false |
Log what would be created without persisting. |
FakerSeed |
null |
Fixed seed for reproducible data. null uses the current time. |
BatchSize |
50 |
Items per database batch. |
ParallelDegree |
4 |
Max parallelism for CPU-bound work (e.g. image generation). |
PublishMode |
FirstSection |
None (draft), All, or FirstSection (publish first root only). |
PublishBatchSize |
50 |
Content items published per batch. 0 = publish immediately. |
DomainSuffix |
"localhost" |
Host used for generated content domains; include port for local dev. |
SkipContentDomains |
false |
Skip creating content domains entirely. |
RebuildCacheAfterSeeding |
true |
Rebuild the published content cache after the run. |
ProgressIntervalPercent |
10 |
Log progress every N percent. 0 disables progress logs. |
EnabledSeeders.* |
all true |
Per-seeder toggles (Languages, Dictionary, DataTypes, DocumentTypes, Media, Content, Users, Members, ContactForm). |
Prefixes.* |
Test_ / testElement / β¦ |
Naming prefixes for generated entities. |
CustomCultures |
null |
Override the built-in 30-culture pool. |
Custom counts (Umbraco.Cms.TestDataSeeder:Configuration)
When Preset = Custom, the Configuration sub-section controls how many of each entity to create.
Top-level sections: Languages, Dictionary, Users, DataTypes, DocumentTypes (with ElementTypes / VariantDocTypes / InvariantDocTypes complexity breakdown plus BlockList / BlockGrid / NestingDepth / MaxBlocksPerEditor), Media (per-type counts for PDF / PNG / JPG / Video), Content (counts, content-complexity distribution, tree shape), and Members (counts, default password, Standard/Premium/VIP distribution).
Full example (appsettings.Test.json)
Combining both sub-sections in one file:
{
"Umbraco.Cms.TestDataSeeder": {
"Options": {
"Enabled": true,
"Preset": "Custom",
"FakerSeed": 42,
"DomainSuffix": "localhost:44340",
"PublishMode": "FirstSection",
"DryRun": false
},
"Configuration": {
"Languages": { "Count": 10 },
"Users": { "Count": 10 },
"Members": { "Count": 20, "StandardPercent": 50, "PremiumPercent": 30, "VIPPercent": 20 },
"Content": { "TotalTarget": 500 },
"Media": { "JPG": { "Count": 100, "FolderCount": 5 } }
}
}
}
Content hierarchy and document types
ContentSeeder produces a 4-level content tree using the doc types generated by DocumentTypeSeeder:
Section (root) β uses simple/medium/complex doc types (with Collection)
βββ Category β uses simple/medium/complex doc types (with Collection)
βββ Page β uses simple/medium/complex doc types (with Collection)
βββ Detail β uses dedicated Detail doc types (no Collection)
Collection (List View). Section, Category, and Page doc types are configured with the built-in "List View β Content" collection and explicit AllowedContentTypes, so each parent renders its children as a list in the backoffice and editors can create children inline.
Detail doc types are leaf nodes β six fixed types (Simple/Medium/Complex Γ Variant/Invariant) with no collection and no allowed children. Detail nodes inherit the same simple/medium/complex distribution as Pages, controlled by Content.DetailSimplePercent / DetailMediumPercent / DetailComplexPercent (default 60/30/10).
The Section/Category/Page complexity mix is driven by Content.SimplePercent / MediumPercent / ComplexPercent (default 50/30/20). Tree shape is controlled by Content.RootSections, CategoriesPerSection, and PagesPerCategory.
Nested blocks
DocumentTypes.NestingDepth controls how deep blocks can be nested inside one another inside seeded Block List and Block Grid editors. Higher values produce more realistic load-test content because they stress JSON serialization, backoffice rendering, and the Delivery API in the same way deeply nested real-world content does.
NestingDepth = 1 means no nesting (blocks contain only simple/medium/complex element data). Each additional level adds one wrapper layer of NestedBlock_DepthN containers, with the leaf level holding the real element data.
NestingDepth = 2:
BlockList
βββ NestedBlock_Depth1
βββ nestedBlocks (BlockList)
βββ Simple / Medium / Complex elements
NestingDepth = 4:
BlockList
βββ NestedBlock_Depth1
βββ nestedBlocks β NestedBlock_Depth2
βββ nestedBlocks β NestedBlock_Depth3
βββ nestedBlocks β Simple / Medium / Complex elements
Preset defaults: Small = 2, Medium = 4, Large = 6, Massive = 8. The MaxBlocksPerEditor option bounds how many distinct block types each Block List / Block Grid data type advertises (default 30); raising it makes block-picker rendering heavier without changing the tree shape.
How seeding runs
TestDataSeederComposer registers all services and an async notification handler for UmbracoApplicationStartedNotification. When the hosting app starts and Options.Enabled is true, SeederOrchestrator runs every registered ISeeder in execution order. Configuration is validated first; if validation fails and StopOnError is true the run is aborted.
Progress is logged via the standard ILogger pipeline. A summary table is emitted at the end of the run.
Test endpoints
For load-test scenarios the package also exposes HTTP endpoints under the umbraco/api/ prefix.
β οΈ Security model. Every endpoint below is anonymous β intentionally so, so load-test runners (k6, JMeter, β¦) can discover targets and exercise contact-form / member-auth flows without managing credentials. This is safe only in dedicated test environments. Do not expose a host running this package to the public internet. See
SECURITY.md.
| Route | Auth | Purpose |
|---|---|---|
GET /umbraco/api/seederstatus/status |
Anonymous | Seeding status, current seeder, elapsed, plus the active Preset and FakerSeed. |
GET /umbraco/api/seederstatus/inventory |
Anonymous | Catalog of seeded root-section URLs, sample content URLs (each annotated with complexity and docType so scripts can pick e.g. "complex Detail pages only"), media URLs, member username pattern, and prefixes. Use this for dynamic target discovery in k6. |
POST /umbraco/api/contactform/submit |
Anonymous (JSON) | Submit a seeded contact-form entry. |
POST /umbraco/api/contactform/form-submit |
Anonymous + anti-forgery | Form-encoded submit (requires @Html.AntiForgeryToken()). |
GET /umbraco/api/contactform/stats |
Anonymous | Contact-form submission stats. |
POST /umbraco/api/memberlogin/login |
Anonymous + anti-forgery | Log a seeded member in (form POST). |
POST /umbraco/api/memberlogin/logout |
Anonymous + anti-forgery | Log out (form POST). |
POST /umbraco/api/memberauth/login |
Anonymous (JSON) | Member auth login. |
POST /umbraco/api/memberauth/logout |
Anonymous (JSON) | Member auth logout. |
GET /umbraco/api/memberauth/me |
Anonymous (reports state) | Returns current member info; reports success=false if no authenticated cookie/session is present. |
/status HTTP response codes
GET /umbraco/api/seederstatus/status returns its JSON body alongside an HTTP status code chosen so CI and load-test scripts can wait for a clean signal without parsing the body:
| Code | When | Status field values |
|---|---|---|
| 200 | Seeding finished (success, with errors, or skipped). | Completed, CompletedWithErrors, Skipped |
| 202 | Seeding hasn't finished yet (still running or waiting to start). | Running, NotStarted |
| 503 | Seeding failed. | Failed |
Shell example β block a CI step until the seeder reports done or fails:
while :; do
code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5000/umbraco/api/seederstatus/status)
case "$code" in
200) echo "seeding complete"; break ;;
503) echo "seeding failed"; exit 1 ;;
*) sleep 5 ;;
esac
done
Resetting between runs
IsAlreadySeeded() checks make the seeder idempotent β re-running with the same configuration is a no-op. To get a clean slate (e.g. to switch presets or change FakerSeed), drop the Umbraco database and let unattended install rebuild it:
rm path/to/Umbraco.sqlite.db
dotnet run
For SQL Server hosts, drop and recreate the database. CI pipelines should provision a fresh database per run rather than mutating a shared one.
Member password handling
The seeded member password is intentionally not returned by any HTTP endpoint β an unauthenticated status endpoint exposing shared credentials is an unnecessary footgun, even in test environments. Load-test scripts should read the password from an out-of-band source:
# in CI / load-test env
export SEEDER_MEMBER_PASSWORD="Test1234!"
Then in the script:
# locust example
import os
password = os.environ["SEEDER_MEMBER_PASSWORD"]
Keep the value in sync with Umbraco.Cms.TestDataSeeder:Configuration:Members:DefaultPassword in the host's appsettings.json.
Locust example
A minimal Locust script that discovers targets from /inventory, picks a random seeded member, logs in on on_start (so the auth cookie persists across the user's tasks), exercises /me and a contact-form submission, and logs out on on_stop. The member count is derived from memberUsernamePattern (e.g. TestMember_{1..30}), so the script tracks whatever the host is configured for without redeploying.
import os
import random
import re
from locust import HttpUser, between, events, task
MEMBER_PASSWORD = os.environ["SEEDER_MEMBER_PASSWORD"] # see "Member password handling" above
# Populated once at test start so every user shares the discovered inventory.
INVENTORY = {}
@events.test_start.add_listener
def on_test_start(environment, **_):
import requests
base = environment.host.rstrip("/")
inv = requests.get(f"{base}/umbraco/api/seederstatus/inventory", verify=False).json()
match = re.search(r"\{1\.\.(\d+)\}", inv["memberUsernamePattern"])
INVENTORY["prefix"] = inv["prefixes"]["member"]
INVENTORY["count"] = int(match.group(1)) if match else 0
INVENTORY["sample_url"] = (inv.get("sampleContentUrls") or [{}])[0].get("url")
class SeederUser(HttpUser):
wait_time = between(1, 3)
def on_start(self):
i = random.randint(1, INVENTORY["count"])
self.username = f"{INVENTORY['prefix']}{i}"
self.client.post(
"/umbraco/api/memberauth/login",
json={"username": self.username, "password": MEMBER_PASSWORD},
name="POST /memberauth/login",
)
def on_stop(self):
self.client.post("/umbraco/api/memberauth/logout", name="POST /memberauth/logout")
@task(3)
def me(self):
self.client.get("/umbraco/api/memberauth/me", name="GET /memberauth/me")
@task(1)
def submit_contact(self):
self.client.post(
"/umbraco/api/contactform/submit",
json={
"name": self.username,
"email": f"{self.username.lower()}@example.com",
"subject": "Load test",
"message": "Hello from Locust",
},
name="POST /contactform/submit",
)
Run with:
SEEDER_MEMBER_PASSWORD=Test1234! locust -f locustfile.py --host https://localhost:44340
Then open the Locust web UI (default http://localhost:8089) to configure users and ramp.
Building from source
dotnet restore
dotnet build
dotnet pack -c Release
Targets .NET 8 and Umbraco [13.0.0, 15.0.0).
Contributing
Issues and pull requests are welcome. Please open an issue first for larger changes so we can discuss the direction.
License
MIT β see LICENSE.md.
Adoption Assessment
A verified publisher signal is available. Assess fit from the package's supported frameworks, license and dependency graph rather than popularity alone.
Related Guides & Workflows
Learn how to evaluate, integrate, or audit this package in your .NET project.