Skip to content
Analytik
Esc
↑↓navigate↵open⌘Jpreview
On this page

Importing data

Bring the history of Umami, Simple Analytics, Matomo, GoatCounter, Google Analytics 4 or PostHog into a site, what each export must contain, and how fields are mapped.

Import adds the past visits, pageviews and custom events from another analytics tool to one of your sites, so the dashboard does not start empty. Imported rows carry the name of their source, are kept apart from what the tracker recorded, and can be removed again without touching tracked data.

  • Importing the same file twice adds nothing the second time, and a run that stopped halfway can be started again with the same file.
  • Rows older than the site’s retention limit are skipped, because the next purge would delete them.
  • Imported and tracked visitors are never merged, even when they cover the same days.
  • Fields a source does not record stay empty and show as unknown, never as zero. Visits without a known length are left out of the average visit duration.

Import in the dashboard

Open Settings, then Data, and find Import data. Choose the source and the export files. The dashboard reads them in your browser and shows a preview before anything is stored: how many visits and events it found, the first and last visit, how many rows it skipped, a note when the site already has visits in those dates, and the first few visits as they will be stored.

Import then sends the visits in small chunks, with a progress bar, so a large file does not hit the request limit of a Worker. Cancel stops after the chunk that is running; choose the same files again to finish, and what is already stored is not added twice. The result lists what was imported, what was already there, what was older than the retention limit, and the first skipped rows with their reason.

Everything an import added is listed under Imported data, with its date range and counts. Remove deletes that import’s visits and events after you confirm the numbers, and leaves everything the tracker recorded.

Nothing is stored when a file is not the one the source exports: the dashboard says which file, or which column, is the problem.

Import from the command line

analytik import --from umami --file events.csv --file sessions.csv --file event-data.csv --site SITE_ID --db "$DATABASE_URL"
analytik import --from simple-analytics --file simple-analytics.csv --site SITE_ID --db sqlite:analytik.db
analytik import --from matomo --file matomo-visits.json --site SITE_ID --db sqlite:analytik.db
analytik import --from goatcounter --file goatcounter-export.csv --site SITE_ID --db sqlite:analytik.db
analytik import --from ga4 --file ga4-events.jsonl --site SITE_ID --db sqlite:analytik.db
analytik import --from posthog --file posthog-events.json --site SITE_ID --db sqlite:analytik.db

--db takes the same values as analytik migrate: postgres://..., sqlite:<path> or a .db file. The command applies pending migrations first, reads and maps every file, and only then writes, so a wrong file stores nothing. It prints how many visits and events were imported, how many were already there, how many were older than the site’s retention limit, and the first skipped rows with their reason.

Cloudflare D1 cannot be reached from the command line. Use the import in Settings, which sends the file in small chunks.

Supported sources

Source What it gives Status
Umami Visits, pageviews and custom events with their details Supported
Simple Analytics Raw pageviews and events Supported
Matomo Visits with their page views, events and goals Supported
GoatCounter Raw hits and events with their session Supported
Google Analytics 4 Raw events, through the BigQuery export Supported
PostHog Events with their properties Supported
Plausible Daily totals only, no visits Not possible

Plausible’s dashboard export is a ZIP of aggregated report CSVs (visitors and pageviews per page, source and country, at most 300 rows each), and its Stats API is aggregated too. Raw events exist only in the Enterprise plan’s daily Parquet export to S3. Individual visits cannot be rebuilt from either. Google Analytics 4’s own reports and API are aggregated; its raw events exist only in the BigQuery export, which is why that is the file Analytik reads. Universal Analytics has no raw export at all.

Umami

Umami keeps its data in three tables. Export each one as CSV, with the column names as they are in the database:

psql "$DATABASE_URL" -c "\copy (SELECT * FROM website_event WHERE website_id = 'WEBSITE_ID') TO 'events.csv' CSV HEADER"
psql "$DATABASE_URL" -c "\copy (SELECT * FROM session WHERE website_id = 'WEBSITE_ID') TO 'sessions.csv' CSV HEADER"
psql "$DATABASE_URL" -c "\copy (SELECT d.* FROM event_data d WHERE d.website_id = 'WEBSITE_ID') TO 'event-data.csv' CSV HEADER"

Only the events file is required. The sessions file adds browser, operating system, device and place, and the event data file adds the properties of custom events. A JSON file with the same fields works too, with either snake_case or camelCase names.

Umami Analytik
visit_id the visit
session_id the visitor
created_at event time; the first one starts the visit
event_type 1 (pageview) a pageview on url_path
event_type 2 and event_name a goal with that name and its event_data properties
referrer_domain, referrer_path, referrer_query the referrer of the visit’s first event, then source and channel as for tracked visits
utm_source, utm_medium, utm_campaign, utm_term, utm_content the campaign fields of the visit
browser, os, device, country, region, city the same fields, written the way the tracker writes them
hostname the visit’s hostname

Umami has no separate visit length, so a visit is as long as the time between its first and last event, which is also how Umami itself counts it. A visit with one event has no known length. Exports from before Umami recorded visit_id are split into visits by session, with a new visit after 30 minutes without events.

Link and pixel events (event_type 3 and 4) and rows without a time, a session or a page are skipped, and the result lists them with their row number.

Simple Analytics

Export the raw data points from Simple Analytics, pages and events together (type=all), and name the fields you want, because leaving fields out returns an older set:

curl -H "Api-Key: $SA_API_KEY" \
  "https://simpleanalytics.com/api/export/datapoints?version=6&format=csv&hostname=example.com&start=2025-01-01&end=2026-03-01&type=all&fields=added_iso,hostname,path,datapoint,is_robot,is_unique,document_referrer,referrer_hostname,referrer_path,utm_source,utm_medium,utm_campaign,utm_content,utm_term,duration_seconds,user_agent,device_type,country_code,browser_name,browser_version,os_name,os_version,screen_width,screen_height,lang_language,lang_region" \
  -o simple-analytics.csv

Only added_iso and path are required. Every other field improves the result, and the browser, system, device and country fields matter most, because they decide which rows belong to one visit.

Simple Analytics keeps no visitor or visit id. Its session_id identifies a single page load, so Analytik cannot use it. A visit is rebuilt from rows that share browser, system, device, country, screen and language, with no gap longer than 30 minutes, and that combination is also the visitor. Two people on identical setups in the same country can end up as one visitor, the same trade-off as a cookieless tracker.

Simple Analytics Analytik
added_iso event time; the first one starts the visit
datapoint = pageview a pageview on path
any other datapoint a goal with that name
document_referrer, or referrer_hostname and referrer_path the referrer of the visit’s first row
utm_* the campaign fields of the visit
duration_seconds summed into the visit length, otherwise first to last row
browser_name, os_name, device_type, country_code the same fields, written the way the tracker writes them

Rows with is_robot set, and rows without a time or a path, are skipped and listed with their row number.

If you import a second export that overlaps the first, no row is stored twice. Exporting the same date range again is the safest way to continue an import that stopped.

Matomo

Matomo has no export of raw visits in its interface, but its Reporting API returns them as JSON. Ask for the visits of one site and one date range with Live.getLastVisitsDetails, in pages so a large site does not time out:

curl "https://matomo.example.com/index.php?module=API&method=Live.getLastVisitsDetails&idSite=1&period=range&date=2025-01-01,2026-03-01&format=json&filter_limit=-1&token_auth=$MATOMO_TOKEN" \
  -o matomo-visits.json

Use a token with view access to the site. A large range can be split into several files and imported one after the other; no visit is stored twice. The file is a list of visits; each one has an actionDetails list with its page views, events and goals.

Matomo Analytik
idVisit the visit
visitorId the visitor
visitDuration the visit length; 0 is left unknown
actionDetails of type action a pageview on the path of its url
actionDetails of type event a goal named eventAction, with category, name and value as properties
actionDetails of type goal a goal named goalName
referrerUrl, referrerName (not for direct visits) the referrer of the visit
campaignSource, campaignMedium, campaignName, campaignKeyword, campaignContent the campaign fields of the visit
browserName, operatingSystem, deviceType, countryCode, region, city the same fields, written the way the tracker writes them

Downloads, outlinks and site searches are skipped and listed with the visit’s position in the file, as are visits without any page view, event or goal.

GoatCounter

In GoatCounter open Settings, then Export, and download the CSV. It holds every hit, with events marked in the Event column. You can pick the file in the dashboard or pass it to analytik import.

GoatCounter keeps a Session per person but no visit id, so a visit ends after 30 minutes without hits, and the session is also the visitor. It records no hostname, visit length or city.

GoatCounter Analytik
Date event time; the first one starts the visit
Path a pageview on that path
Event true a goal named after the path
Referrer and Referrer scheme the referrer; a campaign (c) becomes the campaign source
Browser, System (versions dropped) browser and operating system
Screen size device: under 768 px mobile, under 1024 px tablet
Location country and region

Hits from robots (Bot above 0) and rows without a date or a path are skipped and listed with their row number.

Google Analytics 4

GA4 gives raw events only through its BigQuery export, so the property must have been linked to BigQuery; the export holds data from the day it was linked, not before. Export the events_* tables as JSON (newline-delimited) from BigQuery, for example with bq extract --destination_format=NEWLINE_DELIMITED_JSON to Cloud Storage, and download the files. Pick several files together or one at a time.

A visit is a user_pseudo_id with a ga_session_id, and the user is the visitor. Fields the export nests are read from where GA4 puts them.

GA4 Analytik
event_timestamp (microseconds) event time
page_view a pageview on the path of page_location
any other event a goal with that name
page_referrer, source, medium, campaign (event params) the referrer and campaign of the visit; collected_traffic_source is the fallback
engagement_time_msec (event param) summed into the visit length
device.category, device.operating_system, device.web_info.browser device, system and browser
geo.country, geo.region, geo.city the place

session_start, first_visit, user_engagement and scroll are left out because a page view already says it. Events without a page_location are skipped and listed with their row number. The export’s traffic_source describes the user’s first visit, not the session, so it is not used.

PostHog

Export events with their properties as JSON: the events API (/api/projects/PROJECT_ID/events/, saved page by page), or a batch export to S3, BigQuery or another destination. Properties may be an object or a JSON string; both work, and so does the API’s { "results": [...] } wrapper.

A visit is a $session_id; events without one are split by distinct_id after 30 minutes of silence, and the distinct id is the visitor.

PostHog Analytik
timestamp event time; the first one starts the visit
$pageview a pageview on $pathname or $current_url
an event you named yourself a goal with that name
$referrer, utm_* the referrer and campaign of the visit
$browser, $os, $device_type, $geoip_*, $host the same fields, written the way the tracker writes them

PostHog’s own $ events (autocapture, page leave, identify, feature flags) are left out. Events with no page are skipped and listed.

Was this page helpful?