Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ All notable changes to this project will be documented in this file.
- Fixed main graph being clipped when the browser's root font size is smaller than the default 16px
- Fixed issue with users with billing role not being able to create personal segments
- Fixed period arrow keys hijacking custom-range calendar
- Return a 400 instead of a 500 from the Stats API breakdown endpoint when `page` is not a positive integer

## v3.2.0 - 2026-01-16

Expand Down
15 changes: 13 additions & 2 deletions lib/plausible_web/controllers/api/external_stats_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@ defmodule PlausibleWeb.Api.ExternalStatsController do
:ok <- validate_filters(site, query.filters),
{:ok, metrics} <- parse_and_validate_metrics(params, query),
{:ok, limit} <- validate_or_default_limit(params),
{:ok, page} <- validate_or_default_page(params),
:ok <- ensure_custom_props_access(site, query) do
page = String.to_integer(Map.get(params, "page", "1"))

%{results: results, meta: meta} =
Legacy.Breakdown.breakdown(site, query, metrics, {limit, page})

Expand Down Expand Up @@ -88,6 +87,18 @@ defmodule PlausibleWeb.Api.ExternalStatsController do
@default_breakdown_limit 100
defp validate_or_default_limit(_), do: {:ok, @default_breakdown_limit}

defp validate_or_default_page(%{"page" => page}) do
case Integer.parse(page) do
{page, ""} when page > 0 ->
{:ok, page}

_ ->
{:error, "Please provide page as a positive number."}
end
end

defp validate_or_default_page(_), do: {:ok, 1}

defp parse_and_validate_metrics(params, query) do
metrics =
Map.get(params, "metrics", "visitors")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2492,6 +2492,30 @@ defmodule PlausibleWeb.Api.ExternalStatsController.BreakdownTest do
assert json_response(conn, 400) == %{"error" => @invalid_limit_message}
end

@invalid_page_message "Please provide page as a positive number."

test "returns error with non-integer page", %{conn: conn, site: site} do
conn =
get(conn, "/api/v1/stats/breakdown", %{
"site_id" => site.domain,
"property" => "event:page",
"page" => "foo"
})

assert json_response(conn, 400) == %{"error" => @invalid_page_message}
end

test "returns error with non-positive page", %{conn: conn, site: site} do
conn =
get(conn, "/api/v1/stats/breakdown", %{
"site_id" => site.domain,
"property" => "event:page",
"page" => 0
})

assert json_response(conn, 400) == %{"error" => @invalid_page_message}
end

test "can paginate results", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview, pathname: "/a", timestamp: ~N[2021-01-01 00:00:00]),
Expand Down