# MELPA Scraper (`crawlerbros/melpa-scraper`) Actor

Scrape MELPA - the Milkypostman's Emacs Lisp Package Archive. Search or browse 6,000+ Emacs packages with descriptions, versions, dependencies, maintainers, source repos, and download counts.

- **URL**: https://apify.com/crawlerbros/melpa-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Developer tools, Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## MELPA Scraper

Scrape [MELPA](https://melpa.org) — Milkypostman's Emacs Lisp Package Archive, the largest community package repository for GNU Emacs with 6,000+ packages. Search or browse the full catalog, or fetch specific packages by exact name. Get versions, descriptions, dependencies, keywords, maintainers, source-repo links, and cumulative download counts. Pure HTTP against MELPA's public JSON archives — no auth, no proxy, no cookies.

### What this actor does

- **Two modes:** `search` (free-text query or full browse) and `byNames` (exact package lookup)
- **Two channels:** `melpa` (rolling, ~6,270 packages, rebuilt continuously) and `melpa-stable` (~3,430 packages, built only from tagged releases, semantic versions)
- **Filters:** minimum downloads, source host (GitHub/GitLab/Codeberg/SourceHut/Git/Mercurial), keyword substring, last-updated date range, reverse-dependency lookup (`dependsOn` — find every package that depends on a given library)
- **Sorting:** by download count, name, or most-recently-updated
- **Empty fields are omitted** — you only ever see fields with real data

### Output per package

- `name` — package name (e.g. `magit`)
- `channel` — `melpa` or `melpa-stable`
- `version` — MELPA snapshot version (`YYYYMMDD.HHMM` on `melpa`; semantic version like `4.7.0` on `melpa-stable`)
- `description`
- `packageType` — `tar` (multi-file) or `single`
- `keywords[]`
- `authors[]`, `maintainers[]`, `primaryMaintainer`
- `dependencies[]` — `{name, minVersion}` pairs
- `fetcher` — source host (`github`, `gitlab`, `codeberg`, `sourcehut`, `git`, `hg`)
- `repo` — `owner/repo` (when the host uses that form)
- `branch` — the tracked git branch, when the recipe pins one
- `oldNames[]` — previous package name(s), for packages that were renamed
- `repoUrl` — canonical source-code repository URL
- `commit`, `revisionDescription`
- `downloadCount` — cumulative MELPA installs
- `lastUpdatedDate` — ISO date of the latest build (only on `melpa`; `melpa-stable` versions are tag-derived and don't map to a build date, so this is omitted there)
- `sourceUrl` — the package's MELPA page
- `recordType: "package"`, `scrapedAt`

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `search` | `search` / `byNames` |
| `channel` | string | `melpa` | `melpa` (rolling) / `melpa-stable` (tagged releases only) |
| `searchQuery` | string | – | Free-text query against name/description/keywords (mode=search) |
| `names` | array | – | Exact package names (mode=byNames) |
| `fetcher` | string | – | Filter by source host |
| `minDownloads` | int | – | Minimum cumulative downloads |
| `containsKeyword` | string | – | Substring match on name/description/keywords |
| `dependsOn` | string | – | Only packages that declare this exact package name as a dependency (e.g. `dash`) |
| `updatedAfter` / `updatedBefore` | string | – | ISO date range on last update (mode=search, `melpa` channel only) |
| `sortBy` | string | `downloads` | `downloads` / `name` / `recentlyUpdated` |
| `maxItems` | int | `50` | Hard cap (1–5000) |

#### Example: popular GitHub-hosted packages tagged "completion"

```json
{
  "mode": "search",
  "containsKeyword": "completion",
  "fetcher": "github",
  "minDownloads": 10000,
  "sortBy": "downloads",
  "maxItems": 25
}
```

#### Example: lookup by exact names

```json
{
  "mode": "byNames",
  "names": ["magit", "company", "flycheck", "projectile"]
}
```

#### Example: what depends on `dash`?

```json
{
  "mode": "search",
  "dependsOn": "dash",
  "sortBy": "downloads",
  "maxItems": 25
}
```

#### Example: browse only stable (tagged-release) packages

```json
{
  "mode": "search",
  "channel": "melpa-stable",
  "searchQuery": "completion",
  "sortBy": "downloads",
  "maxItems": 25
}
```

### Use cases

- Track the most-downloaded Emacs packages over time
- Build a dependency graph across the MELPA ecosystem
- Audit which packages are hosted on which forge (GitHub vs GitLab vs self-hosted git)
- Monitor recently-updated packages for a curated Emacs starter-kit

### FAQ

**Does this require a MELPA account or API key?** No — MELPA publishes its full catalog as public JSON with no authentication.

**How current is the data?** MELPA rebuilds its archive continuously from each package's source repository; this actor reads the live `archive.json`/`recipes.json`/`download_counts.json` on every run.

**Can I get every package in one run?** Yes — leave `searchQuery` blank and set `maxItems` to `5000` (there are currently ~6,270 packages on `melpa`, ~3,430 on `melpa-stable`).

**What's the difference between `melpa` and `melpa-stable`?** `melpa` rebuilds continuously from each package's default branch, so versions are snapshot-dated (`YYYYMMDD.HHMM`) and can change several times a day. `melpa-stable` only rebuilds when a maintainer pushes a git tag, so versions look like conventional semantic versions (e.g. `4.7.0`) and change far less often — pick this channel if you want fewer, more conservative releases. Not every `melpa` package has a tagged release, so `melpa-stable` has fewer packages overall.

# Actor input Schema

## `mode` (type: `string`):

What to fetch.

## `searchQuery` (type: `string`):

Free-text query matched (case-insensitive substring) against package name, description, and keywords. Leave blank to browse all packages.

## `names` (type: `array`):

Exact MELPA package names, e.g. `magit`, `company`, `flycheck`.

## `channel` (type: `string`):

Which MELPA archive to read. `melpa` is the default rolling archive, rebuilt continuously from each package's default branch (~6,270 packages, snapshot-dated versions). `melpa-stable` only includes packages built from tagged releases (~3,430 packages, semantic versions like `4.7.0`) — use this if you want fewer, more conservative releases.

## `fetcher` (type: `string`):

Filter to packages hosted on a specific source-code host.

## `minDownloads` (type: `integer`):

Only include packages with at least this many cumulative MELPA downloads.

## `containsKeyword` (type: `string`):

Only include packages whose name, description, or keyword tags contain this substring (case-insensitive).

## `dependsOn` (type: `string`):

Only include packages that declare this exact package name as a dependency (case-insensitive). Useful for finding what depends on a given library, e.g. `dash`, `s`, `emacs`.

## `updatedAfter` (type: `string`):

Only include packages last updated on/after this date.

## `updatedBefore` (type: `string`):

Only include packages last updated on/before this date.

## `sortBy` (type: `string`):

Order of emitted records (mode=search).

## `maxItems` (type: `integer`):

Hard cap on emitted records.

## Actor input object example

```json
{
  "mode": "search",
  "searchQuery": "json",
  "names": [],
  "channel": "melpa",
  "fetcher": "",
  "sortBy": "downloads",
  "maxItems": 50
}
```

# Actor output Schema

## `packages` (type: `string`):

Dataset containing all scraped MELPA packages.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "search",
    "searchQuery": "json",
    "names": [],
    "channel": "melpa",
    "fetcher": "",
    "sortBy": "downloads",
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/melpa-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "search",
    "searchQuery": "json",
    "names": [],
    "channel": "melpa",
    "fetcher": "",
    "sortBy": "downloads",
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/melpa-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "search",
  "searchQuery": "json",
  "names": [],
  "channel": "melpa",
  "fetcher": "",
  "sortBy": "downloads",
  "maxItems": 50
}' |
apify call crawlerbros/melpa-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlerbros/melpa-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/zSs965G5u4t1Y5x8T/builds/sp4eqTSBkdDoFctxV/openapi.json
