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 metadataStandard (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.
| Domain | Pipeline Stages | Board Group |
|---|---|---|
| Deals | lead → qualified → offered → closed_won / closed_lost | stage |
| Issues | backlog → todo → in_progress → in_review → done | status |
| Tickets | new → in_progress → waiting_on_customer → resolved → closed | status |
| Campaigns | draft → active → paused → completed | status |
| Users | onboarding → active → inactive → offboarded | status |
| Services | evaluating → active → deprecated → inactive | status |
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.