CLI tool for migrating historical analytics events from Plausible ClickHouse into a Rybbit deployment.
The importer:
- reads source events directly from Plausible ClickHouse
- inspects the source schema before querying so it can handle common Plausible schema variations
- resolves an existing Rybbit site or creates one in Postgres when configured to do so
- inserts converted events into Rybbit ClickHouse
- preserves timestamps, paths, query params, referrers, browser and OS data, geography, channel data, and custom event properties
- Node.js 20+
- network access to the source Plausible ClickHouse instance
- network access to the destination Rybbit ClickHouse and Postgres instances
npm install
cp .env.example .envChoose one Plausible source selector:
PLAUSIBLE_SITE_IDPLAUSIBLE_SITE_DOMAINPLAUSIBLE_HOSTNAME
PLAUSIBLE_SITE_ID is usually the safest option when you know the numeric site id.
Choose one destination selector:
RYBBIT_SITE_IDRYBBIT_SITE_DOMAIN
Required variables:
PLAUSIBLE_CLICKHOUSE_URL=http://localhost:18123
PLAUSIBLE_CLICKHOUSE_DATABASE=plausible_events_db
PLAUSIBLE_CLICKHOUSE_USERNAME=default
PLAUSIBLE_CLICKHOUSE_PASSWORD=
# Choose one Plausible source selector
# PLAUSIBLE_SITE_ID=1
PLAUSIBLE_SITE_DOMAIN=example.com
# PLAUSIBLE_HOSTNAME=example.com
RYBBIT_CLICKHOUSE_URL=http://localhost:8123
RYBBIT_CLICKHOUSE_DATABASE=analytics
RYBBIT_CLICKHOUSE_USERNAME=default
RYBBIT_CLICKHOUSE_PASSWORD=
RYBBIT_POSTGRES_URL=postgres://user:password@localhost:5432/analytics
# Choose one destination selector
# RYBBIT_SITE_ID=1
RYBBIT_SITE_DOMAIN=example.comIf the destination site does not exist yet, set CREATE_SITE_IF_MISSING=true and also provide:
RYBBIT_ORGANIZATION_ID=<organization-id>
RYBBIT_CREATED_BY_USER_ID=<user-id>Useful optional variables:
PLAUSIBLE_EVENT_TABLE=events_v2
ALLOW_INSECURE_TLS=false
IMPORT_BATCH_SIZE=1000
DRY_RUN=false
VERBOSE=true
RYBBIT_IMPORT_ID=
RYBBIT_SITE_NAME=Imported Site
RYBBIT_SITE_STRING_ID=
RYBBIT_SITE_API_KEY=
RYBBIT_SITE_PUBLIC=false
RYBBIT_SALT_USER_IDS=false
RYBBIT_BLOCK_BOTS=false
RYBBIT_TRACK_IP=false
HOSTNAME_MODE=preserve
START_AT=
END_AT=
END_BEFORE=Prefer START_AT with END_BEFORE for bounded backfills because it creates a half-open window: [START_AT, END_BEFORE).
Dry run:
DRY_RUN=true npm run importImport data:
npm run importThe CLI prints a JSON summary with imported row counts, skipped rows, batch counts, and the discovered source schema.
- The importer runs
DESCRIBE TABLEagainst the Plausible event table before building the source query. - If the source table has no
domaincolumn, site filtering falls back tohostnamewhen possible. - Source table names are validated as safe SQL identifiers before query construction.
- Pageviews are written as
type=pageviewand non-pageview events are written astype=custom_event. - Plausible
meta.keyandmeta.valuearrays are converted into Rybbit event properties. - When
CREATE_SITE_IF_MISSING=true, the importer inserts a new row into Rybbit'ssitestable. - For multi-host imports, keep
HOSTNAME_MODE=preserveunless you explicitly want imported events rewritten to the destination domain.
Run unit tests:
npm testRun the Docker-based end-to-end check:
npm run e2eThe E2E runner starts disposable Plausible and Rybbit dependencies, seeds sample source data, runs the importer, and verifies that events were written to Rybbit.