drift Docs
Start
What is Drift?
The tour, if you are new here.
Use cases
Whether Drift does your thing.
Getting started
Nothing to deployed, in one command.
Architecture
How a slice is put together.
What it costs
The free grant, four unit prices, two rules.
Build
Canvas
Static sites, same origin as your API.
Tools
Operate
Auth
Accounts, tokens and scopes.
Security
Boundaries, sandboxing and hardening.
Troubleshooting
Error codes
What went wrong, and what to do about it.
Legal
Acceptable use
What a slice may not be used for.
Data processing
The DPA, and every sub-processor.

Backbone data & state

The backbone section says what to seed and configure: which collections to fill, which databases to build a schema in, which queues to create, and the secrets and cache entries to set.

Sizes live in the slice's shape.

Every storage limit — per collection, per database, per bucket — and the slice-wide dials (blob object size, queue depth, secret size, locks, realtime connections) are chosen in the form that prices them. The keys for them here still parse and are ignored.

A collection, bucket or database has to exist before anything can be written to it. The slice refuses a write naming one it does not have with 400 rather than creating it, so the set is declared where the shape lives. What an entry below does is name a slot the slice already holds and say what to put in it.

NoSQL collections

nosql is a list of maps, one per collection. slot names the collection this project uses; the long form also accepts a seed, a JSONL file (one JSON document per line) that the deploy upserts by each document's _id, and/or a ttl.

Driftfile
nosql:
    - slot: submissions                       # used, nothing to seed
    - slot: permit-types
      seed: ./backbone/permit-types.jsonl       # seeded from JSONL
    - slot: sessions
      ttl: 30d                                  # reaped 30 days after last write

name is the old spelling of slot.

It still parses and is rewritten for you. The rename is the point of the entry changing meaning: it references a collection the slice holds rather than declaring one into existence.
# permit-types.jsonl: one JSON document per line, each with an _id
{"_id": "residential-extension", "name": "Residential extension"}
{"_id": "new-build-residential", "name": "New-build residential"}

Seeding is idempotent (re-deploying re-upserts changed rows) and non-destructive (documents your app added at runtime are left alone, because their _ids aren't in the seed file). Every line must be valid JSON with a non-empty _id.

ttl (<int>s/m/h/d) deletes a document once its last write is older than the TTL. The clock resets on every update, so only genuinely stale, untouched documents are reaped. There's no undo: documents aren't soft-deleted or archived anywhere. Omit it and documents are kept forever. Applies per-collection, not per-document.

SQL databases

sql is a list of per-slice SQLite databases, addressed by name. Each entry materialises as one .db file and is reached from the SDK as drift.Backbone.SQL("name") for relational queries and transactions. An entry names a database the slice already holds and says what to build in it: a schema and/or a seed. A database with neither needs no entry at all.

Driftfile
sql:
    - name: app
      schema: ./backbone/app.sql        # idempotent DDL, runs on every deploy
      seed:   ./backbone/app-seed.sql   # runs only on the first deploy

schema is applied on every deploy, so keep it idempotent (CREATE TABLE IF NOT EXISTS …). seed runs only when the database has no user tables yet, which is the first deploy, so later deploys never re-run it, even if you change the file. To re-run a seed, start from an empty database again: drop it with drift backbone sql drop <name> and redeploy, and the deploy recreates the database and re-applies the schema and seed. A database name is 1–64 characters ([a-z0-9], then [a-z0-9_-], then [a-z0-9]) and becomes both the .db filename and the SDK lookup key.

TypeDefaultMeaning
  • name
    Type
    string
    Default
    required
    Meaning
    Database identifier and SDK lookup key, as in drift.Backbone.SQL("name"). It references a database the slice holds.
  • schema
    Type
    path
    Default
    none
    Meaning
    SQL file of idempotent DDL, applied on every deploy.
  • seed
    Type
    path
    Default
    none
    Meaning
    SQL file run only on the first deploy (when the database has no user tables yet).

Read the Backbone SQL guide → for querying and transactions from your functions.

Blob buckets

blobs is a list of the buckets this project uses. A bucket has no seed or schema, so an entry does nothing but record that the project writes to it — and buckets are sized in the slice's shape, so a project can leave the section out entirely. Code doesn't reach a bucket through a handle the way SQL("name") does; drift.Backbone.Blob.Put/.Get take one path-shaped name (e.g. "uploads/receipt-42.pdf"), where everything before the first / is the bucket, and a name with no / goes to the default bucket.

Driftfile
blobs:
    - name: uploads
TypeDefaultMeaning
  • name
    Type
    string
    Default
    required
    Meaning
    Bucket name: the prefix before the / in a blob's path-shaped name. It references a bucket the slice holds.

A bucket has to be declared before anything can be written to it. A put naming a bucket this slice does not declare — default included — is refused with 400, and the same holds for an undeclared collection. See Blobs and NoSQL.

Queues

A list of the queues this project uses. Queues can't be seeded, because seeding messages on every deploy would re-fire work, so an entry is a name and nothing more. Each queue's depth is set in the slice's shape, which also carries a default for one that names no depth of its own.

Driftfile
queues:
    - validate                          # bare name
    - name: notify                    # long form: same thing, room to grow

The map form takes name and nothing else. It exists so per-queue options can arrive without breaking manifests; until they do, any other key inside it is rejected rather than ignored, so a typo fails the deploy instead of silently doing nothing.

Secrets

A map of KEY: value. A value of $NAME (unquoted) is a reference resolved when drift file apply runs. The literal never touches the Driftfile or the wire, only the resolved string travels. Quote a value to force a literal that starts with $.

Driftfile
secrets:
    MUNICIPALITY_NAME: "Amsterdam"      # hardcoded literal
    RESEND_API_KEY:    $RESEND_API_KEY   # resolved at deploy time

A $NAME reference (and any ${VAR} placeholder elsewhere in the Driftfile) resolves through a variable origin hierarchy, highest precedence first:

  1. Hardcoded: a literal value written in the Driftfile. Absolute.
  2. Environment: a variable exported in your shell session.
  3. Override flags: --secret KEY=value (repeatable), and --env <name> (same as the positional environment; also sets ENV). An override yields to a variable the environment set; it beats the env files.
  4. .env.<env> file: when an environment is selected, the per-environment secrets file (e.g. .env.staging) next to the Driftfile. Out-ranks the base .env.
  5. .env file: the base KEY=value file next to the Driftfile, sourced automatically (lowest precedence; fills only what nothing above provided). Pass --no-env-file to skip the env files.

So an exported DB_PASSWORD beats --secret DB_PASSWORD=…, which beats a DB_PASSWORD line in .env.staging, which beats one in .env. A deploy prints what it loaded from which env file, and what it ignored because the environment supplied it, so the layering is never silent. Config dials that differ per environment belong in environments:; only secrets belong in .env.<env>.

Cache

A map from key to a value to pre-warm at deploy. The short form is a file path whose contents become the cached value. The long form takes an inline value (or a file) plus an optional ttl in seconds, after which the entry expires and a Cache.Get returns empty until your code re-populates it. Omit ttl (or set 0) for an entry that never expires.

Driftfile
cache:
    menu: ./backbone/menu.json          # short form: file contents become the value
    banner:                            # long form: inline value with a TTL
      value: "Closed for maintenance"
      ttl:   3600                       # seconds; 0 / omitted = never expires
    pricelist:                         # long form: file + TTL
      file: ./backbone/prices.json
      ttl:  86400

At runtime the cache is read/write from your functions, so these entries are just the initial warm set. Read the Backbone Cache guide →