Marketplace Stats shows sales and usage statistics for paid plugins on the JetBrains Marketplace. It is a web application: you start it on your computer or server and open the reports in your browser.
Features:
- Reports of sales, licenses, customers, trials, countries, subscriptions, resellers, prices, and the usage of offers and discounts
- Charts of the metrics of the tables, e.g. the sales of the last 12 months per day, with moving averages, vertical lines for releases and your own events
- A summary of all your paid plugins
- An optional cache on disk, so that restarts are fast
- An optional refresh in the background, so that the pages don't wait for the Marketplace
- An MCP server, so that AI agents can analyze your data
This is a new version of marketplace-stats. It is written in Kotlin instead of Go.
- Quick Start
- Requirements
- Configuration
- Persistent Cache
- Automatic Refresh
- Custom Events in the Charts
- MCP Server for AI Agents
- Chart Data for AI Agents
- Building From Source
- Libraries
- License
You need an API key of the JetBrains Marketplace. You create it in your account on the JetBrains Marketplace.
The Docker image is jansorg/jetbrains-marketplace-stats.
docker run \
--env MARKETPLACE_API_KEY="your-api-key" \
--publish 8080:8080 \
--volume marketplace-stats-cache:/root/.cache/marketplace-stats \
jansorg/jetbrains-marketplace-stats:latestOpen http://localhost:8080 in your browser.
The image stores the persistent cache in /root/.cache/marketplace-stats.
The volume keeps the cache when you replace the container, e.g. with a new version.
The file docker-compose.yml is a template. It explains all settings in comments.
- Copy
docker-compose.ymlto your server. - Replace
your-api-keywith your API key. - Optional: enable the automatic refresh and the custom events.
- Run
docker-compose upto test the setup. Rundocker-compose up -dto start it in the background.
Download marketplace-stats-all.jar from the releases on GitHub.
This JAR file contains all dependencies.
java -jar marketplace-stats-all.jar --api-key="your-api-key"Open http://localhost:8080 in your browser.
- To run the JAR file: Java 25 or later.
- To build the application: JDK 27.
- The Docker image already contains Java.
There are three ways to configure the application. You can combine them.
- Command line options, e.g.
--port=3030. - Environment variables, e.g.
MARKETPLACE_SERVER_PORT=3030. They are useful with Docker. - A configuration file in JSON format. Its path is the last argument, e.g.
java -jar marketplace-stats-all.jar /path/to/config.json. A command line option or an environment variable overrides the value of the file.
| Option | Environment variable | Key of the config file | Default | Description |
|---|---|---|---|---|
-k, --api-key |
MARKETPLACE_API_KEY |
apiKey |
— | The API key of the JetBrains Marketplace. Required. |
-h, --host |
MARKETPLACE_SERVER_HOSTNAME |
— | 0.0.0.0 |
The IP address or hostname of the web server. |
-p, --port |
MARKETPLACE_SERVER_PORT |
— | 8080 |
The port of the web server. |
-c, --currency |
MARKETPLACE_DISPLAY_CURRENCY |
displayedCurrency |
USD |
The currency of the shown amounts, e.g. EUR. |
--show-reseller-charges |
MARKETPLACE_SHOW_RESELLER_CHARGES |
showResellerCharges |
off | Show the incorrect charges of sales through resellers. |
--cache-dir |
MARKETPLACE_CACHE_DIR |
cacheDir |
none | The directory of the persistent cache. Without it, the data is only kept in memory. |
--auto-refresh[=<minutes>] |
MARKETPLACE_AUTO_REFRESH |
— | off | Load new data in the background, every 30 minutes or every <minutes>. See Automatic Refresh. |
--rate-limit |
MARKETPLACE_RATE_LIMIT |
rateLimit |
10 |
The maximum number of requests per second to each server, e.g. the Marketplace. 0 disables the limit. |
--events |
MARKETPLACE_EVENTS_FILE |
eventsFile |
none | A YAML file with your own events for the charts. See Custom Events. |
--all-plugins-summary, --no-all-plugins-summary |
MARKETPLACE_ALL_PLUGINS_SUMMARY |
— | on | Show the sales and downloads of all paid plugins above the list of plugins. |
--mcp, --no-mcp |
MARKETPLACE_MCP |
— | on | Serve the MCP server at /mcp. |
--mcp-allowed-hosts |
MARKETPLACE_MCP_ALLOWED_HOSTS |
— | none | More hostnames for the MCP server, comma-separated. See Security. |
-d, --debug |
MARKETPLACE_LOG_LEVEL |
— | none |
The log level: none, normal or verbose. |
Use true or false as the value of the environment variables of options that are on or off.
java -jar marketplace-stats-all.jar --help prints all options.
The settings --cache-dir and --events contain a path:
- A leading
~is your home directory. This also works in environment variables and in the configuration file. - A relative path in the configuration file is relative to the directory of the configuration file.
- A relative path of an option or of an environment variable is relative to the working directory.
config-template.json is a template:
{
"apiKey": "your-api-key",
"displayedCurrency": "USD"
}The file contains your API key. Don't share it, and don't add it to a Git repository.
The application needs many requests to the Marketplace to load the data of a plugin. By default, it only keeps the data in memory. After a restart, it loads all data again.
With --cache-dir, the application also stores the data in a directory:
java -jar marketplace-stats-all.jar \
--api-key="your-api-key" \
--cache-dir="~/.cache/marketplace-stats"The Docker image uses a cache in /root/.cache/marketplace-stats by default. An empty MARKETPLACE_CACHE_DIR disables it.
| Data | Loaded again |
|---|---|
| Sales and trials of the last 45 days | After each start (in the background), and when a report needs data older than 30 minutes |
| All sales and trials | Once a week |
| Downloads, ratings, releases | Once a day |
| Products and releases of JetBrains | Once a day, unchanged data isn't downloaded again |
| Prices | Once a week |
| Exchange rates | After six hours |
After a restart, the reports show the stored data immediately. If the stored data is older than a day, e.g. after a long downtime, the application loads the recent data before it shows the report.
The button "Drop caches" in the footer of a report deletes the cached data of the plugin. The application then loads all data again.
- Each API key has its own directory. The API key itself is not stored.
- The products of JetBrains are stored once for all API keys.
- The directory contains sales and customer data. Protect it like other private data.
- You can delete the directory at any time. The application then loads all data again.
- The application ignores and replaces damaged files and files of other versions.
The Marketplace API is slow. Without the automatic refresh, a report loads new data when the cached data is older than 30 minutes. The page waits for this.
With the automatic refresh, the application loads new data in the background. The pages always use the cached data and don't wait.
java -jar marketplace-stats-all.jar \
--api-key="your-api-key" \
--cache-dir="~/.cache/marketplace-stats" \
--auto-refresh--auto-refreshloads new data every 30 minutes.--auto-refresh=15loads it every 15 minutes.- With Docker, use
MARKETPLACE_AUTO_REFRESH=30. The value is the interval in minutes.0disables it. - The automatic refresh is off by default.
How it works:
- The application only refreshes plugins that you opened: a report page of the plugin, a request of the MCP server for the plugin, or the summary of all plugins on the start page. It doesn't refresh other plugins.
- 24 hours after the last request for a plugin, the refresh of this plugin stops. The next request loads the data again and starts the refresh again.
- Each refresh loads the recent data, e.g. the sales and trials of the last 45 days. Data that changes less often, e.g. downloads or prices, is only loaded when it would be outdated before the next refresh.
- If a refresh fails, the application keeps the previous data and uses it.
The line charts can show vertical lines for the releases of your plugin, its price changes, the date at which it disabled the continuity discount and the releases of IntelliJ IDEA. Other events are not part of the data of the Marketplace, e.g. a blog post or a change of the trial period. You can add such events with a YAML file.
events:
- id: price-announcement # optional: the ID of the event, the default is the date and the label
plugin: 12345 # required: the ID of the plugin
date: 2026-10-16 # required: the date in the format YYYY-MM-DD
label: "Announced price change" # required: the text above the line
- trial-period: # a key without a value is also the ID
plugin: 12345
date: 2026-03-01
label: "Trial period of 30 days"- You find the ID of a plugin in the address of its report, e.g.
?pluginId=12345. - The IDs of the events must be unique.
events-template.ymlis a template. Copy it, e.g. toevents.yml, and change the events.
Set the path of the file with --events, with MARKETPLACE_EVENTS_FILE or with eventsFile in the configuration file:
java -jar marketplace-stats-all.jar \
--api-key="your-api-key" \
--events="~/marketplace-stats/events.yaml"In the chart dialog, the menu "Events" lists the events of the plugin, followed by the releases of the plugin, its price changes and the releases of IntelliJ IDEA. Select an event to show it as a vertical line. Your browser stores the selection. The summary of all plugins on the start page shows the events of all paid plugins, because it only contains the paid plugins.
The application reads the file again when you change it. You don't need to restart it.
- If the file is invalid when the application starts, the application doesn't start. The error message names the event and the problem.
- If you change the file and it becomes invalid, the application writes the error to the log and keeps the previous events.
Mount the directory of the file into the container, read-only. Then set the path of the file in the container:
environment:
- MARKETPLACE_EVENTS_FILE=/config/events.yaml
volumes:
- ./config:/config:roMount the directory, not the file. Many editors replace a file when they save it. A mounted file would then keep its old content in the container.
The web server also provides an MCP server at /mcp, e.g. http://localhost:8080/mcp.
AI agents can use it to analyze the sales and the trends of your plugins.
It uses the Streamable HTTP transport and has no sessions.
For example, to add it to Claude Code:
claude mcp add --transport http marketplace-stats http://localhost:8080/mcp| Tool | Data |
|---|---|
list_plugins |
The account of the API key, its plugins with their IDs, and the monetization of paid plugins |
| Tool | Data |
|---|---|
get_yearly_summary |
Sales, fees, ARR, combined churn, downloads and trials per year |
get_monthly_overview |
Sales, licenses, MRR, ARR, churn, downloads and trials per month |
get_daily_summary |
Sales, downloads and trials per day |
get_sales_and_trials_breakdown |
Sales by license period and customer type, trials by country |
get_top_countries |
Sales, trials and trial conversion by country, sorted by sales or trials |
get_mrr_movements |
Changes of the MRR by new, expansion, contraction, churned and reactivated customers (experimental) |
get_revenue_retention |
Gross and net revenue retention per month (experimental) |
get_customer_cohorts |
Customer and revenue retention of the customers by the year of their first payment (experimental) |
get_lifetime_value |
Realized lifetime value per cohort year (experimental) |
get_annual_renewal_rates |
Renewal rates of annual subscriptions per year (experimental) |
get_monthly_renewal_rates |
Renewal rates of monthly subscriptions per month (experimental) |
get_average_revenue_per_customer |
Paying customers and average recurring revenue per customer (experimental) |
| Tool | Data |
|---|---|
list_sales |
Sales with customer, amounts and line items, with filters, totals and grouping |
list_customers |
Customers with status (active or inactive), active, churned and transferred licenses and total sales |
list_licenses |
Licenses with status, validity and renewals, e.g. licenses that expire in a month |
list_customer_licenses |
Licenses of a customer with their status and purchases |
list_trials |
Single trials with customer and conversion, and a summary per month |
get_license_cohorts |
Renewals of the subscription licenses by the month of their first paid subscription |
list_license_transfers |
Licenses bought by another customer than before, e.g. moved from an individual to an organization |
| Tool | Data |
|---|---|
get_pricing |
Current prices by country, customer type and license period, countries with the same prices are grouped |
get_price_changes |
Changes of the annual base price, derived from the sales |
list_offers |
Special offers with discount or free months and the number of their coupons, without the codes |
get_offer_usage |
Usage of offers and discounts (page Offers), metrics of the discount amounts of the offers |
get_exchange_rates |
ECB exchange rates of a date, and the currencies supported by the currency argument |
| Tool | Data |
|---|---|
get_downloads |
Downloads per month or day by IDE product, plugin version, or both |
list_reviews |
Reviews with rating and date, average rating per month, current rating |
get_page_views |
Visitors of the plugin page by day, country and source (sampled Google Analytics data) |
| Tool | Data |
|---|---|
list_plugin_releases |
Releases of a plugin, the first release of each major version, optionally the results of the verifiers |
list_jetbrains_products |
JetBrains products with their metadata |
list_jetbrains_product_releases |
Versions of a JetBrains product with date and build, or its major releases |
get_jetbrains_product_release |
Metadata of a release of a JetBrains product, optionally with what's new, downloads and patches |
| Tool | Data |
|---|---|
create_chart |
A line or bar chart of metrics over time as PNG or SVG, with the options of the chart dialog |
| Tool | Data |
|---|---|
refresh_data |
Deletes the cached data of a plugin or of all plugins, like the button "Drop caches" of the reports |
-
Tables: the table tools return the tables of the overview page of a plugin as JSON. They take the ID of the plugin and an optional date range
startDateandendDate(YYYY-MM-DD, inclusive). Without a date range, they return all data. -
Tables per day, month or year: these are calculated with all data, and only the rows of the date range are returned. This keeps values like the recurring revenue and the churn correct. The other tables only use the sales, licenses and trials of the date range.
-
Columns and values: each column has a unique key, a title and a unit, e.g. the currency or
%. The rows contain plain numbers. Values of the current period are marked as incomplete. Churn rates include the numbers of churned and previously active licenses. -
Lists: the other tools return lists of sales, customers, licenses, trials, reviews, prices and releases. Lists accept
limitandoffsetand return the total count. -
Amounts: amounts are in the displayed currency. They are also in USD and in the currency of the customer, if these are different. Sales include the fee of the Marketplace. The tools with amounts accept an optional
currency, e.g.EUR. The amounts are then converted with the ECB exchange rate of the day of each sale.get_exchange_rateslists the supported currencies. -
Enum values are lower camel case, e.g.
individualorannual. -
Conventions: the instructions of the MCP server describe the conventions once, not in each tool. This saves tokens. Unknown arguments are rejected.
-
Charts:
create_chartrenders the charts of the web interface on the server, e.g. to show them in a chat or to add them to a report. Its arguments are the options of the chart dialog:pluginId, without it the summary of all paid plugins.series: up to 8 lines, each with ametric(the IDs of/chart-metrics, e.g.sales,trials,mrr,churn-annual), an optionalwindow(follow,day,week,month,quarter,year,calendar-month,calendar-quarter,calendar-year), an optional movingaverageof 1, 3 or 12 months, astyle(linesorbars) and acolor. Example:[{"metric": "sales"}, {"metric": "sales", "average": 12}, {"metric": "trials", "style": "bars"}].detail(year,month,week,day), the defaultwindowandstyleof the series.- The shown dates:
range(all,today,past-7-days,this-monthor a year like2025) orstartDateandendDate. - The options of the View menu:
previousYear,showIncomplete,extremes(min,max),hideAxisValues, and the vertical lines ofmarkers(release,price,continuity,ide,events). format(pngorsvg),widthandheight(default 1000 × 500) andscale(the pixel density of the PNG).
A chart shows at most two units on two y-axes, e.g. amounts and counts. The result has a JSON description of the series with their latest values, followed by the image: a PNG as image content (
image/png), which clients like Claude show and pass to the model, and an SVG as text content with the SVG markup, because most clients don't accept SVG images. The PNG needs fonts, the Docker image has them.
Limits of the data: the Marketplace API has no downloads by IDE version and no installs. The page views of
get_page_views are sampled data of Google Analytics. The Marketplace warns that page visits and referrals may be
inaccurate.
The MCP server has no authentication, like the web interface. To protect against DNS rebinding attacks, it only accepts
requests for the hostnames localhost, 127.0.0.1, [::1] and the hostname of --host.
- If you use another hostname, e.g. with Docker on a server, add it with
--mcp-allowed-hosts. --no-mcpdisables the MCP server.
"Copy data URL" in the Export menu of a line chart copies an address, which returns the values of the shown lines of
the chart, e.g. to paste it into a chat with an AI agent or to load it with curl. The address describes the chart:
the lines with their windows and moving averages, the lines of the previous year, the detail level and the selected
range. Hidden lines are left out. Only charts of metrics have an address. Charts of table columns without a metric
and pie charts aren't available.
http://localhost:8080/chart-export?pluginId=12345&detail=month&lines=sales:calendar-month,sales@avg3:calendar-month,mrr&from=2025-01-01&to=2025-12-31
| Parameter | Description |
|---|---|
pluginId |
The plugin. Without it, the summary of all paid plugins. |
lines |
Up to 32 lines, separated by commas: <metric>[@avg<1|3|12>][@previous-year][:<window>], e.g. sales@avg3:year. The windows are day, week, month, quarter, year, calendar-month, calendar-quarter and calendar-year. Without a window, the calendar window of the detail level. Values at a date like the MRR ignore the window. |
detail |
The dates of the values: day, week, month (default) or year, and today. A line with a shorter window has finer dates, e.g. each day for sales:day at the level month. |
from, to |
The first and the last date, ISO dates, optional. |
incomplete |
false leaves out the values, which may still change, e.g. of the current month. |
format |
csv (default) or json. |
The default format is CSV: one row per date and one column per line, the headers contain the label of the line and
its unit, e.g. "Sales before fees, calendar month (EUR)". Numbers have a dot as decimal separator and no grouping,
dates are ISO dates, an empty cell has no value. The last column status marks the dates with incomplete values
(they may still change) and partial values (their window starts before the first data). CSV needs the fewest tokens
for agents and opens in spreadsheets. format=json returns the same values with metadata: the plugin, the currency,
the time of the export and, for each line, its metric, unit, measure (flow, stock or ratio), window, detail
level and the dates of its incomplete and partial values. Its rows are arrays of the date and the values.
Errors are answered with status 400 or 404 and a text.
The address needs no cookies, but like the web interface it's only reachable where the server is, e.g. by an agent on the same computer.
You need JDK 27. Gradle downloads it, if it isn't installed.
./gradlew build # compiles the code and runs the tests
./gradlew shadowJar # creates build/libs/marketplace-stats-all.jarThe client of the API and the reports are published as Maven libraries to the repository https://maven.j-a.dev/releases,
with the version of the application. The repository isn't public.
| Artifact | Contents |
|---|---|
dev.j-a.jetbrains-marketplace:marketplace-client:<version> |
Client of the API of the JetBrains Marketplace and of the services of JetBrains, caching, exchange rates |
dev.j-a.jetbrains-marketplace:marketplace-data:<version> |
Reports of the sales, licenses, customers and trials, it depends on marketplace-client |
They need Java 25 or later. They are licensed under the AGPL-3.0-or-later, like the application. The libraries log with SLF4J, add a backend like Logback to see their logs.
./gradlew publishToMavenLocal publishes the libraries to the local Maven repository.
deploy.bash publishes a release to https://maven.j-a.dev/releases, with:
./gradlew publishAllPublicationsToJaDevRepository --no-configuration-cacheIt needs the Gradle properties jaDevUsername and jaDevPassword, the name and the secret of an access token of the
repository, e.g. in ~/.gradle/gradle.properties or as the environment variables ORG_GRADLE_PROJECT_jaDevUsername and
ORG_GRADLE_PROJECT_jaDevPassword. A version ending with -SNAPSHOT is published to https://maven.j-a.dev/snapshots.
A version can't be published twice, update VERSION.txt before.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU AFFERO GENERAL PUBLIC LICENSE for more details.
You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE along with this program. If not, see https://www.gnu.org/licenses/.