Emend
Build

Storage Configuration

Configure where Emend stores data, including SQLite (default), Supabase, and PostgreSQL for production deployments.

Storage configuration determines where Emend stores data.

Default Storage (Open Source)

The open-source version uses SQLite by default — no storage configuration is needed. Emend automatically creates and manages a local SQLite database, so you can start using it immediately without any setup.

# No storage configuration required — SQLite is used automaticallyclient = EmendClient()  # see Quickstart for connection optionsconfig = client.get_config()# config.storage_config is already set to SQLite

Supabase Storage

Hosted Enterprise
Hosted Enterprise uses Supabase with automatic provisioning and managed infrastructure. Hosted accounts start with managed storage and can switch to their own Supabase project from Settings.

For production deployments that need managed cloud storage, you can configure Supabase manually:

from emend.models.config_schema import StorageConfigSupabase storage = StorageConfigSupabase(    url="https://your-project.supabase.co",    key="your_service_role_key",    db_url="postgresql://emend_user:replace-me@host:5432/postgres",    # Optional reader credentials for search traffic. Search reads use these    # credentials; writes and consistency-sensitive reads still use url/key.    read_url="https://your-reader.supabase.co",    read_key="your_reader_service_role_key",)config.storage_config = storageclient.set_config(config)
PropTypeDescription
urlstrSupabase project URL
keystrSupabase service-role key (needs write access)
db_urlstrPostgreSQL connection string
read_urlstrOptional. Supabase reader URL for search. Defaults to url.
read_keystrOptional. Reader key for search. Defaults to key.
schemastrOptional. Used by hosted Emend for platform-managed per-org schemas. Omit this for bring-your-own Supabase storage.
Info
Search uses reader credentials when provided, so search results can be bounded-stale on replicated infrastructure. Mutations and publish/generation consistency reads continue to use the writer credentials.

PostgreSQL Storage

Hosted Enterprise
Emend Enterprise can also store data in a customer-owned PostgreSQL database, such as AWS RDS, without running Supabase or PostgREST. Auth/login storage remains separate from data storage.
from emend.models.config_schema import StorageConfigPostgres storage = StorageConfigPostgres(    db_url="postgresql://emend_user:replace-me@host:5432/database",    schema="public",    # Optional reader pool for search traffic    read_db_url="postgresql://emend_reader:replace-me@reader:5432/database",    read_pool_size=10,)config.storage_config = storageclient.set_config(config)
PropTypeDescription
db_urlstrPostgreSQL connection string
schemastrOptional. Target schema for Emend data. Defaults to public.
pool_sizeintOptional. Maximum direct SQL connections per process (per organization). Defaults to 10.
pool_acquire_timeoutfloatOptional. Seconds a query waits for a free pooled connection before failing. Defaults to 30.0.
read_db_urlstrOptional. Reader PostgreSQL connection string for search. Defaults to db_url.
read_pool_sizeintOptional. Maximum reader connections per process. Defaults to pool_size.
read_pool_acquire_timeoutfloatOptional. Seconds search waits for a free reader connection. Defaults to pool_acquire_timeout.
Warning
PostgreSQL storage requires PostgreSQL 15+ with pgvector available.

The pool is shared by all concurrent work for an organization. When in-flight queries exceed pool_size, additional queries queue for up to pool_acquire_timeout seconds rather than failing immediately; only a query that waits longer than the timeout raises an error. Because pool_size is per organization, the total server-side connection count scales with the number of active organizations — size it against your database's max_connections.

In self-host deployments, pool settings can be set without editing config via EMEND_POSTGRES_POOL_SIZE, EMEND_POSTGRES_POOL_ACQUIRE_TIMEOUT, EMEND_POSTGRES_READ_DB_URL, EMEND_POSTGRES_READ_POOL_SIZE, and EMEND_POSTGRES_READ_POOL_ACQUIRE_TIMEOUT.

Search/read timeout knobs are also available for production deployments: EMEND_SUPABASE_HTTP_TIMEOUT_SECONDS for Supabase/PostgREST and EMEND_POSTGRES_STATEMENT_TIMEOUT_MS for native Postgres.

Config Encryption

Hosted Enterprise
Enterprise can encrypt stored organization configuration before it is written to the configuration store. This protects persisted configuration_json rows, such as stored storage credentials; it does not encrypt the Emend data tables themselves. Configure FERNET_KEYS with a comma-separated key ring. The first valid key encrypts new writes, and older keys are accepted for reads during rotation. Leaving FERNET_KEYS empty is valid and stores configuration as plaintext.

Generate a key with:

uv run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Warning
Keep Fernet keys in your secret manager or deployment environment; never commit them. Set FERNET_REQUIRED=true only after valid keys are deployed and existing configuration rows have been re-encrypted. Required mode fails closed: if no valid Fernet key is available, Emend refuses to store plaintext configuration. During key rotation, deploy the new key first in the FERNET_KEYS list, re-encrypt existing rows, then remove retired keys after all running instances can read the newly encrypted values.

Row Retention

Emend applies high-water row limits to data tables on the publish path. When an eligible table reaches its limit, the server deletes the oldest 20% of rows for that table by its target-specific ordering column; not every target uses created_at.

# Defaults to 250000 rows per targetEMEND_ROW_LIMIT_INTERACTIONS=500000EMEND_ROW_LIMIT_PROFILES=250000 # Set a target to 0 to disable its cleanupEMEND_ROW_LIMIT_PLAYBOOK_OPTIMIZATION_EVENTS=0 # Defaults to 300 seconds. Set to 0 or a negative value to disable the periodic# cleanup sweep.EMEND_RETENTION_CLEANUP_INTERVAL_SECONDS=300

The legacy INTERACTION_CLEANUP_THRESHOLD variable still applies to interactions when EMEND_ROW_LIMIT_INTERACTIONS is unset.

The user_playbook_exposure_events target is fixed at 250,000 rows in code and does not accept a EMEND_ROW_LIMIT_USER_PLAYBOOK_EXPOSURE_EVENTS override.

Most retention targets are ordered by created_at. The exceptions are: offline_tuner_reward_label rows use label_created_at, and user_playbook_exposure_events rows use ingested_at. Exposure events also have a 14-day minimum age floor, so row-count cleanup cannot delete them until they are at least 14 days old.

Enterprise Self-Host Single Database

Hosted Enterprise
Enterprise self-host deployments can run with one customer-owned database for both login metadata and Emend data. Set DEPLOYMENT_MODE=self_host, choose EMEND_STORAGE=supabase or EMEND_STORAGE=postgres, and provide SELF_HOST_USERNAME / SELF_HOST_PASSWORD for the only login account.

For Supabase self-host, configure DATA_SUPABASE_URL, DATA_SUPABASE_KEY, and DATA_DB_URL. For vanilla Postgres self-host, configure DATA_DB_URL. Startup applies auth and data migrations to that same database and stores the generated configuration_json in public.organizations.

Usage-metering WAL volume (self-host)

Hosted Enterprise
EMEND_SELF_HOST_METERING=required|exempt selects the self-host metering composition. Unset or blank defaults to required. Only a trusted deployment owner may deliberately select exempt for an intentionally unmetered installation; paid deployment tooling must not set it accidentally.

In required mode, usage is written to a small encrypted write-ahead log (WAL) on disk rather than your database. Mount a persistent, writable volume at EMEND_USAGE_WAL_PATH (default ~/.emend/usage_wal) for restart-safe metering. Boot fails when the resolved directory is not writable; an ephemeral or default path logs a warning.

If the control plane temporarily cannot complete a usage-ingest cardinality read, it returns HTTP 503 with Retry-After: 5. The shipper retains the batch in its WAL and retries without double-counting acknowledged usage. Keep the WAL volume intact while database availability recovers.

For multi-instance deployments, configure one shared persistent mount root and let startup create derived per-instance directories at <root>/<instance-id>. Distinct per-replica persistent volumes are an alternative when every replica receives a stable, unique path. Emend's generated, file-backed instance id is preferred; set EMEND_INSTANCE_ID only when the orchestrator guarantees uniqueness. If required-mode WAL files become unreadable, recover the original preserved files rather than starting from an empty directory.

In exempt mode, Emend does not construct or validate the usage WAL, and no WAL path, persistence, or mount is required. Early startup may still normalize or derive a configured path environment value before selecting the exempt composition.

Activating a Self-Host Data Plane

Hosted Enterprise
When Emend provisions your self-host account, the admin console's Onboard self-host customer flow shows a one-time activation key. Configure your data plane with that key — the control-plane URL defaults to Emend cloud.
# The activation key shown once in the Emend admin portal.BYOC_DEPLOYMENT_SECRET="rflx-dep-…"# Optional — the Emend control-plane base URL. Defaults to the Emend cloud# control plane (https://www.emend.online); set it only to target a different one.# CONTROL_PLANE_URL="https://www.emend.online"

CONTROL_PLANE_URL is the canonical setting. Older deployments that still set CONTROL_PLANE_INGEST_URL continue to work as a fallback, but new deployments should use CONTROL_PLANE_URL.

On startup the data plane calls POST /api/billing/byoc/activate with the activation key in the X-Deployment-Secret header. The control plane confirms the key→deployment binding and returns your deployment id, org id, and central public key.

After activation, the data plane ships usage and pulls its entitlement lease automatically. You do not need to set BYOC_DEPLOYMENT_ID or CONTROL_PLANE_PUBLIC_KEY; the activation handshake supplies them.

Warning
The activation key is your deployment's credential: keep it secret, and rotate it from the admin portal if exposed. Credential enforcement for legacy BYOC registration and ingest is controlled on the control plane with BYOC_ENFORCE_REGISTER_CREDENTIAL and BYOC_ENFORCE_INGEST_CREDENTIAL. Data plane operators normally do not set those flags; the data plane only needs the per-deployment activation key shown above.