This is a standalone OpenClaw plugin (@apify/apify-openclaw-plugin) that provides web scraping and data extraction via Apify's API. It registers 1 agent tool (apify) — a universal scraper with 3 actions: discover, start, and collect.
- Upstream repo: https://github.com/openclaw/openclaw
- Plugin docs: https://docs.openclaw.ai/plugins/community
- Agent tools guide: https://docs.openclaw.ai/plugins/agent-tools
- Plugin system docs: https://docs.openclaw.ai/plugins
- Apify console: https://console.apify.com/
- Support: integrations@apify.com
src/
index.ts # Plugin entry point — registers apify + CLI
apify-client.ts # Shared Apify client factory, config helpers
cli.ts # openclaw apify setup|status|test commands
util.ts # Inlined utilities: ToolInputError, normalizeSecretInput, wrapExternalContent
tools/
apify-scraper-tool.ts # Universal scraper — discover + start + collect
test/
helpers.ts # makeMockFetch, standardRunResponses, TEST_CONFIG
apify-scraper.test.ts # Tool tests
openclaw.plugin.json # Plugin manifest (configSchema + uiHints) — REQUIRED
package.json # npm package config
Single tool with 3 actions:
| Action | Purpose |
|---|---|
discover + query |
Search Apify Store for actors by keyword |
discover + actorId |
Fetch actor's input schema + README |
start + actorId + input |
Fire an actor run, returns runId/datasetId |
collect + runs |
Poll status, return results for completed runs |
The tool description includes instructions for the agent:
- Sub-agent delegation: Tool should be used by a sub-agent that returns only relevant extracted data, not raw dumps.
- Batching: Batch multiple URLs into a single run (e.g.
startUrls: [{url: "..."}, ...]). - Known actors: Compact comma-separated list of 57 actors across Instagram, Facebook, TikTok, YouTube, Google Maps, and more.
- Support: Directs users to integrations@apify.com for issues.
- Single tool, multiple actions: All scraping goes through
apifywithdiscover/start/collectactions. - Async two-phase pattern:
startreturns immediately with run references.collectpolls and fetches results. The agent does other work between calls. apify-clientSDK: Uses the officialapify-clientnpm package (not raw HTTP). Client created viacreateApifyClient(apiKey, baseUrl).- Inlined utilities (
util.ts):ToolInputError,normalizeSecretInput, andwrapExternalContentare NOT exported fromopenclaw/plugin-sdk. We carry local copies. - No build step: OpenClaw loads plugins via
jiti(TypeScript JIT). We ship.tssource directly. - No skills: Skills were removed — the tool description and
discoveraction provide all needed guidance.
Format: username~actor-name (tilde separator, not slash).
The ~ format avoids URL path ambiguity. The discover action builds slugs as ${username}~${name}.
openclaw apify setup (in src/cli.ts) writes config directly to the OpenClaw config file.
It uses:
api.runtime.config.loadConfig()→ returns currentOpenClawConfigapi.runtime.config.writeConfigFile(cfg)→ writes it back to disk
The wizard merges safely: preserves existing config, adds to tools.alsoAllow without duplicates, uses group:plugins when all tools are selected.
- Runtime: Node 22+ (required by openclaw peer dependency).
- Install:
npm install - Type-check:
npx tsc --noEmit - Test:
npx vitest run - Pack (dry run):
npm pack --dry-run - Current state: 1 test file, 10 tests passing.
- TypeScript (ESM). Prefer strict typing; avoid
any. - Tool names:
snake_case(e.g.,apify). - Plugin id / config keys:
kebab-case(e.g.,apify). - Keep files concise. Add comments for non-obvious logic.
- Tool schema guardrails: avoid
Type.Union. UsestringEnumfor string enums,Type.Optional(...)instead of nullable types.
This section documents how OpenClaw discovers, loads, and runs plugins. Reference this when modifying the plugin or debugging integration issues.
Discovery → Manifest Loading → Config Validation → Module Loading → Registration → Tool Resolution
OpenClaw scans for plugins in strict precedence order:
- Config paths (
plugins.load.paths) — highest priority - Workspace extensions (
<workspace>/.openclaw/extensions/) - Global extensions (
~/.config/openclaw/extensions/or~/.openclaw/extensions/) - Bundled extensions (shipped with OpenClaw) — lowest priority
For npm-installed plugins: openclaw plugins install <npm-spec> runs npm pack, extracts the tarball into ~/.openclaw/extensions/<id>/, and runs npm install --ignore-scripts for dependencies.
The plugin id is derived from the unscoped npm package name. For @apify/apify-openclaw-plugin, the id = apify.
Every plugin must ship openclaw.plugin.json in its root. This file is loaded without executing plugin code and provides:
id(string, required) — canonical plugin identifierconfigSchema(JSON Schema, required) — validated before code runsuiHints(optional) — field labels, placeholders, sensitive flags for the Control UI
Plugin config from plugins.entries.<id>.config is validated against the manifest's configSchema using JSON Schema. Invalid config blocks the plugin from loading entirely.
OpenClaw uses Jiti to import the plugin entry file with an alias from "openclaw/plugin-sdk" → the local OpenClaw SDK.
Critical gotcha: If register() returns a Promise, the async work is silently ignored. All registration must be synchronous.
Our plugin calls api.registerTool(tool) for apify and api.registerCli(...) for the apify CLI subcommand.
Tool names that collide with core tool names are silently dropped. Plugin tools are gated by allowlists (tools.alsoAllow).
{
plugins: {
enabled: true,
entries: {
"apify": {
enabled: true,
config: {
apiKey: "apify_api_...", // or use APIFY_API_KEY env var
baseUrl: "https://api.apify.com",
maxResults: 20,
enabledTools: [], // empty = all tools enabled
},
},
},
},
tools: {
alsoAllow: ["group:plugins"], // or "apify" or "apify"
},
}Config changes require a gateway restart (openclaw gateway restart).
All scraped data is untrusted external content. The wrapExternalContent(content, options) function (in util.ts) sanitizes marker strings and wraps content between boundary markers with source metadata.
- API keys: Resolved from plugin config
apiKeyorAPIFY_API_KEYenv var. Never logged or included in tool output. - Base URL validation: Only
https://api.apify.comprefix allowed. Rejects other URLs to prevent SSRF. - External content wrapping: All scraped results wrapped with untrusted content markers.
- Async
register()is ignored. Must be synchronous. Ourregister()is sync. openclaw.plugin.jsonis mandatory. Without it, the plugin never loads.- Tool name collisions are silent drops. If a tool name collides with a core tool, it gets skipped.
- Config validation happens before code. Invalid config = plugin won't load.
workspace:*deps break outside the monorepo. We use"openclaw": "^2026.2.18"in devDependencies.- Plugin tools are gated by allowlists. Users must add tool names or
group:pluginstotools.alsoAllow. - No
Type.Unionin schemas. OpenClaw rejectsanyOf/oneOf/allOf. UsestringEnum()andType.Optional().