# Composer Repository Extractor (`datamule/composer-repository-extractor`) Actor

Point at any Composer / Satis / Packagist-style PHP package repository (wpackagist, Roundcube, Packagist, private Satis) and get one structured row per package: version, license, authors, keywords, require, source/dist.

- **URL**: https://apify.com/datamule/composer-repository-extractor.md
- **Developed by:** [Datamule](https://apify.com/datamule) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.35 / 1,000 packages

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

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

## Composer Repository Extractor

**Point at any Composer / Satis / Packagist-style PHP package repository and get one clean, structured row per package** — version, license, authors, keywords, `require` / `require-dev`, and source/dist locations — straight from the repository's own metadata feed. No HTML scraping, no browser.

The Composer repository protocol is **self-describing**: a repo's root `packages.json` advertises exactly how per-package metadata is reached, so **one actor works against every repository** — the huge public mirrors, a vendor's plugin feed, or your own private Satis / Toran / Repman / Private Packagist behind a token.

### Why this one

Most Composer/Packagist tools on the market only ever hit **packagist.org central**. This actor speaks the underlying **repository protocol**, so it runs against *any* self-hosted or specialised feed the same way it runs against Packagist:

- **wPackagist** (`https://wpackagist.org`) — every WordPress plugin & theme as a Composer package
- **Roundcube plugins** (`https://plugins.roundcube.net`)
- **Packagist** itself (`https://repo.packagist.org/packages.json`)
- **Your private Satis / Toran / Repman / Private Packagist** — pass a bearer token or custom header

It automatically handles **both** metadata schemes — the modern **Composer v2** `metadata-url` (`/p2/<vendor>/<name>.json`, delta-encoded) and the older **v1** `providers-url` + `provider-includes` chain (content-hash resolution) — so you never have to know which one a given repo speaks.

### Input

| Field | Type | Description |
|---|---|---|
| `repoUrl` | string (required) | Repository base URL or its `packages.json` URL. e.g. `https://wpackagist.org` |
| `packages` | array | **Packages mode.** Explicit `vendor/name` list to fetch directly (works on any repo). Leave empty to enumerate. |
| `vendorFilter` | string | Enumerate mode: keep only packages whose name starts with `<vendor>/`. |
| `packageFilter` | string | Enumerate mode: case-insensitive substring on the full `vendor/name`. |
| `maxPackages` | integer | Global cap on emitted rows (default 100). Enumeration stops at the cap. |
| `discoveryOnly` | boolean | **Discovery mode.** Emit one row describing the repo (protocol, templates, provider-include count) without fetching packages. |
| `bearer` | string (secret) | Optional bearer token for a private repo. Never logged. |
| `extraHeaders` | object | Optional extra HTTP headers (e.g. Basic auth for a gated Satis). Never logged. |

The **run mode is auto-selected**: set `packages` → *packages* mode; set `discoveryOnly` → *discovery* mode; otherwise *enumerate* the repo with your filters.

### Output

One flat, fully-nullable row per package:

`name`, `vendor`, `packageName`, `type`, `description`, `latestVersion`, `versionCount`, `versions[]`, `license[]`, `authors[]`, `keywords[]`, `homepage`, `time`, `require`, `requireDev`, `suggest`, `provide`, `sourceType`/`sourceUrl`/`sourceReference`, `distType`/`distUrl`/`distReference`, `abandoned`, plus a lossless `_raw` copy of the latest version object and `_protocol` / `_repoUrl` provenance.

#### Example

```json
{
  "name": "monolog/monolog",
  "vendor": "monolog",
  "type": "library",
  "latestVersion": "3.10.0",
  "versionCount": 87,
  "license": ["MIT"],
  "keywords": ["log", "logging", "psr-3"],
  "require": { "php": ">=8.1", "psr/log": "^2.0 || ^3.0" },
  "sourceUrl": "https://github.com/Seldaek/monolog.git",
  "distUrl": "https://api.github.com/repos/Seldaek/monolog/zipball/...",
  "_protocol": "v2-metadata"
}
```

### Pricing

Pay-per-event: **one `package` event per package row** returned. Discovery mode emits a single row.

### Notes

- Reads only public metadata over HTTPS; no login-walled scraping.
- Every field is read defensively by key presence, so a repo that omits optional fields yields `null` for those columns rather than failing.
- If the URL isn't a Composer repository (an HTML page, a 404), the run fails fast with a clear message instead of emitting empty rows.

# Actor input Schema

## `repoUrl` (type: `string`):

Base URL of a Composer / Satis / Packagist-style PHP package repository, or its packages.json URL directly. The Composer repository protocol is self-describing: the root packages.json advertises how per-package metadata is reached, so ONE actor works against any self-hosted feed — wpackagist.org (WordPress plugins/themes), plugins.roundcube.net, Packagist itself, or a private Satis / Toran / Repman / Private Packagist. Examples: https://wpackagist.org or https://repo.packagist.org/packages.json . The run MODE is chosen automatically from the other fields you set (see below).

## `packages` (type: `array`):

PACKAGES MODE. An explicit list of 'vendor/name' packages to fetch directly (e.g. monolog/monolog, symfony/console). Works on every repo — for a Composer v1 repo the content hash is resolved automatically from the provider map. Leave EMPTY to ENUMERATE the repository instead. Fastest way to pull a known set of packages from any repo.

## `vendorFilter` (type: `string`):

Optional vendor prefix used to narrow enumeration, e.g. 'wpackagist-plugin' or 'symfony'. Only packages whose name begins with '<vendor>/' are kept. Leave empty to enumerate all vendors.

## `packageFilter` (type: `string`):

Optional case-insensitive substring matched against the full 'vendor/name'. Only matching packages are kept during enumeration, e.g. 'cache' matches symfony/cache and wpackagist-plugin/wp-super-cache. Leave empty for no name filter.

## `maxPackages` (type: `integer`):

A GLOBAL cap on the number of package rows to emit (each row is one package and one billable event). Enumeration stops as soon as the cap is reached, so a small value is a cheap, deterministic sample. Large public repos (wpackagist ~90k packages) can return a very large set — keep this bounded. Default 100.

## `discoveryOnly` (type: `boolean`):

DISCOVERY MODE. When true, the actor resolves the root packages.json and emits a SINGLE row describing the repository itself: its protocol (v2 metadata-url / v1 providers-url / inline), the advertised metadata + provider templates, how many provider-includes it exposes, and its available-package patterns. A cheap way to inspect what any Composer repo supports before a full run.

## `bearer` (type: `string`):

Optional bearer token for a private / auth-gated repository (sent as the Authorization: Bearer header). Not required for public repos. Never logged.

## `extraHeaders` (type: `object`):

Optional extra HTTP headers as a JSON object, e.g. {"Authorization": "Basic ..."} for an HTTP-auth Satis, or a custom API header for Private Packagist. Not required for public repos. Header values are never logged.

## Actor input object example

```json
{
  "repoUrl": "https://wpackagist.org",
  "maxPackages": 100,
  "discoveryOnly": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# 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 = {
    "repoUrl": "https://wpackagist.org",
    "maxPackages": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("datamule/composer-repository-extractor").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 = {
    "repoUrl": "https://wpackagist.org",
    "maxPackages": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("datamule/composer-repository-extractor").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "repoUrl": "https://wpackagist.org",
  "maxPackages": 100
}' |
apify call datamule/composer-repository-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=datamule/composer-repository-extractor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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