# Demo data and packs BottleCRM has two independent ways to populate an organization with non-empty data: the `seed_data` management command, which generates a large, randomized dataset for an entire org, and **vertical packs**, which apply a small, curated, industry-specific set of pipelines and sample records to an org that already exists. ## Seeding a demo organization ```bash uv run python manage.py seed_data --email you@example.com ``` Run from `backend/` (or `docker compose exec backend python manage.py seed_data --email you@example.com` against the Docker stack). This is the command referenced in [Docker quick start](docker-quick-start.md) and [First sign-in](first-sign-in.md). The command (`backend/common/management/commands/seed_data.py`) creates an organization and fills it with realistic-looking fictional data, contacts, accounts, leads, opportunities, cases, tasks, products, invoices, estimates, recurring invoices, sales goals, teams and tags, using curated name/company/address pools and reserved demo domains (`example.com`, `.example`) so nothing it generates resembles a real person or business. The **first** organization it creates is always named **`MicroPyramid`**, specifically so local-dev workflows have a stable, known org name to sign in against with `devlogin --org MicroPyramid`. An **admin profile** is created for whichever `--email` you pass. Re-running the command with the same email reuses the existing org and user rather than creating duplicates. It's safe to run more than once. Counts for every entity type (`--leads`, `--accounts`, `--opportunities`, `--invoices`, and so on) and locale (`--currency`, `--country`) are configurable flags; see `--help` for the full list. A `--clear` flag deletes existing CRM data for re-seeding (but never users, orgs or profiles): it's a separate, broader operation from the sample-data clearing described below, and is not scoped to records a pack created. ## Vertical packs A vertical pack is a small, declarative JSON manifest, not a data generator, that shapes an org toward one industry: a starter pipeline (with stages) for leads, cases and tasks; a handful of custom field definitions; tags; products; some terminology overrides (for example relabeling "Lead" as "Enquiry"); and a short list of named sample records (accounts, contacts, deals, tickets, leads, tasks) that reference each other by name within the manifest. Four packs ship in `backend/common/packs/manifests/`, one manifest file per pack: | Pack id | File | Description | |---|---|---| | `real-estate` | `real-estate.json` | Brokers, agencies and developers selling residential and commercial property. | | `professional-services` | `professional-services.json` | Agencies, consultancies and accounting firms selling billable work. | | `education-admissions` | `education-admissions.json` | Coaching centres, training institutes and colleges managing admissions. | | `events-weddings` | `events-weddings.json` | Wedding planners, event agencies and venues taking enquiries through to a delivered event. | Every manifest is validated strictly at load time (`backend/common/packs/schema.py`). An unknown key, an invalid `stage_type`, a `sample_data` reference to an account or stage name that isn't declared elsewhere in the same pack, and similar mistakes all fail validation rather than being silently ignored or half-applied. ## Applying a pack Applying a pack is handled by `backend/common/packs/applier.py`'s `apply_pack()`, reachable through: - **The UI**: the Settings → Organization page (for an existing org) and the organization creation page (for a brand-new org) both offer a pack picker, calling the same backend endpoint. Both exist in the web app and the mobile app. On creation the picker asks "What kind of business is this?" and defaults to "Skip for now". It is hidden when the pack list cannot be loaded. A pack that fails to apply never undoes the new org; the mobile app says so and points to Settings → Organization, where the pack can be applied again. - **The API**: `POST /api/packs//apply/`, restricted to an organization admin (`request.profile.role == "ADMIN"`); any authenticated member can list available packs via `GET /api/packs/`, but only an admin can apply one. **Applying a pack is strictly additive.** The applier's own description of itself: it creates what's missing and skips what already exists, and it "never calls `.update()`, never calls `.save()` on a row it did not just construct, and never deletes." A pipeline whose name already exists in the org is skipped entirely (along with its stages); a custom field, tag or product that already exists by its matching key is skipped individually. This makes applying the same pack twice, or applying a second pack to an org that already has one, safe: it can only add what's still missing, never overwrite or remove something you've since changed. The one exception is terminology and the org's `vertical` label, which are merged in (only keys not already set are added) rather than duplicated. Sample records created by a pack (accounts, contacts, deals/opportunities, tickets/cases, leads and tasks) are marked with an `is_sample` boolean field, set only by the applier. It is `read_only` on every serializer that exposes it, so it can never be set by a client request. Every sample row is also tagged with a "Sample data" tag and assigned to whichever admin applied the pack, purely for visibility in the UI; the tag carries no special authority and isn't what the system uses to know a row is sample data. Sample dates are written relative to one day, which the manifest records as `sample_dates_written_on`. When the pack is applied, every sample date (task due dates, deal close dates, and values of custom fields whose `field_type` is `date`) moves by the days between that day and today in the org's timezone. A task written as due the next day is due the day after the pack is applied, and a deal that closed three weeks earlier still closed three weeks earlier. A manifest whose sample rows hold dates without `sample_dates_written_on` fails validation. If an org already has any sample rows (from this pack or a previous one), applying `sample_data` again is skipped. A pack's sample data is only ever created once per org, regardless of how many times you re-apply. ## Clearing sample data **Only `clear_sample_data()` deletes sample records**, via `DELETE /api/packs/sample-data/` (also admin-only, also reachable from the Settings → Organization page). It is the sole counterpart to pack-applied sample data: nothing else in the system deletes rows just because `is_sample` is set. Clearing is scoped strictly to rows where `is_sample=True` for the calling org, deleted in an order that removes child records (tasks, leads) before the parents they can reference (tickets, deals, contacts, accounts). It never removes anything else. A real record you created yourself is untouched even if it happens to reference a sample record, and a sample row is **retained rather than deleted** if a non-sample record still depends on it (for example, a real invoice raised against a demo account). Retained rows are reported back, not silently skipped, so the caller knows some sample data was intentionally kept rather than assuming the clear was incomplete. Sample rows that belong to a module the org has switched off (see [Modules](modules.md)) are kept too, and the response counts them under `switched_off_by_type`.