Bucket & Entities

How Based stores data in your S3 bucket, and how to organize entities for real business operations.

The Bucket is the Database

Based doesn't use a traditional database. Every piece of data is a JSON file in an S3-compatible bucket. The S3WORM library provides typed access, schema inference, and CRUD operations on top of flat files. Directory names at the bucket root ARE your schema — any S3 tool, backup script, data pipeline, or AI agent can immediately understand your org's data shape by listing top-level prefixes.

Layout Philosophy

Org-wide entities are flat at the top level of the bucket. Each entity domain gets its own directory. Sub-entities nest under their parent domain. This keeps the bucket navigable with any S3 client, CLI, or script — no application required to understand the data.

Flat by default

Top-level directories = entity domains. customers/, issues/, marketing/ — not nested 5 levels deep. Deeper nesting is reserved for sub-entities within a domain.

Domain-grouped sub-entities

CRM has contacts, companies, and deals. Marketing has campaigns, channels, and conversions. Sub-entities nest under their domain: crm/contacts/, crm/deals/.

App metadata is namespaced

Based's internal entities (pages, views, members) live under .based/ — separate from your business data. Both are S3WORM entities with the same API.

Grow with the business

Start with users/ and crm/. Add issues/ when you build product. Add marketing/ when you track campaigns. The bucket grows organically as the org's needs evolve.

Recommended Bucket Structure

Three tiers depending on your org's complexity. Start minimal and add domains as needed.

Minimal (any org)

bucket: {org-name}
├── users/                    # who works here
├── registry/
│   └── services/             # external services + API specs
└── .based/                   # Based app metadata

Standard (most companies)

bucket: {org-name}
├── users/
├── registry/
│   └── services/
├── crm/
│   ├── contacts/             # people
│   ├── companies/            # organizations
│   └── deals/                # pipeline opportunities
├── issues/                   # product dev tracking
├── support/
│   └── tickets/              # customer service
├── assets/
│   └── uploads/
└── .based/

Full (growth-stage company)

bucket: {org-name}
├── users/
├── registry/
│   └── services/
├── crm/
│   ├── contacts/
│   ├── companies/
│   └── deals/
├── issues/
├── marketing/
│   ├── campaigns/            # coordinated marketing efforts
│   ├── channels/             # where campaigns run
│   └── conversions/          # campaign spend → revenue attribution
├── support/
│   └── tickets/
├── finance/
│   ├── invoices/
│   └── subscriptions/
├── products/
├── orders/
├── assets/
│   └── uploads/
└── .based/

Entity Domains

Each domain serves a core business function. See the Entity Reference for full schemas and examples.

users/

Users

Identity layer. Every person in the org. All other entities reference users by ID.

registry/services/

Service Registry

External services the org depends on. OpenAPI specs, auth config (env var refs, not secrets), cost tracking.

crm/

CRM

Contacts (people), companies (orgs), and deals (pipeline: lead → qualified → offered → closed).

issues/

Product Development

Issue tracking with daily progress logs. IC work toward the roadmap. Sprint boards and milestone views.

marketing/

Marketing & Advertising

Campaigns, channels, and conversions. Track spend → impressions → leads → customers → revenue.

support/tickets/

Customer Service

Customer-reported issues, questions, requests. SLA tracking, satisfaction surveys, links to dev issues.

Pipeline & Status Conventions

Every entity domain has lifecycle states that power Kanban board views. Drag entities between columns to update status.

DomainPipeline StagesBoard Group
Dealslead → qualified → offered → closed_won / closed_loststage
Issuesbacklog → todo → in_progress → in_review → donestatus
Ticketsnew → in_progress → waiting_on_customer → resolved → closedstatus
Campaignsdraft → active → paused → completedstatus
Usersonboarding → active → inactive → offboardedstatus
Servicesevaluating → active → deprecated → inactivestatus

Advanced: Organizing by Functional Unit

For larger orgs, the flat top-level structure can be extended to organize by department. This is optional — most companies should start flat and only nest by unit when they need clear data boundaries or S3 bucket policy restrictions by prefix.

bucket: large-corp
├── engineering/
│   ├── issues/
│   ├── deployments/
│   └── incidents/
├── sales/
│   ├── crm/
│   │   ├── contacts/
│   │   ├── companies/
│   │   └── deals/
│   └── proposals/
├── marketing/
│   ├── campaigns/
│   ├── channels/
│   └── conversions/
├── support/
│   └── tickets/
├── finance/
│   ├── invoices/
│   └── subscriptions/
├── hr/
│   ├── candidates/
│   ├── employees/
│   └── reviews/
├── users/
├── registry/
│   └── services/
└── .based/

Trade-off: deeper nesting means longer entity paths in Database Block configs. crm/contacts/ becomes sales/crm/contacts/. Keep paths as short as practical.

Entity Relationships

Entities reference each other by ID. In Database Block view schemas, ID fields are declared as type: "relation" with a target path so the UI renders them as clickable chips.

users/usr_xK9mR2.json
  │
  ├── crm/contacts/cont_001.json          (ownerId → users/)
  │     └── crm/companies/comp_001.json   (companyId → crm/companies/)
  │
  ├── crm/deals/deal_001.json             (ownerId → users/)
  │     └── marketing/conversions/conv_001.json  (dealId → crm/deals/)
  │           └── marketing/campaigns/camp_001.json
  │
  ├── issues/iss_001.json                 (assigneeId → users/)
  │     └── support/tickets/tkt_001.json  (relatedIssueId → issues/)
  │
  └── support/tickets/tkt_001.json        (assigneeId → users/)

Oplog & Recovery

Every mutation (create, update, delete) is logged to the oplog at .worm/oplog/. This provides:

  • Full audit trail of every change
  • Point-in-time rollback to any previous version
  • Soft delete — entities go to trash, recoverable within retention period
  • Periodic snapshots for fast reconstruction

Local Development

Set S3_MODE=local in your .env to use the local filesystem at .worm/data/ as your bucket. JSON files are read and written directly — no S3 required. The bucket layout is identical whether local or remote.