Build your first JavaScript Apify actor

A step-by-step walkthrough that takes you from zero to a working, deployable JavaScript actor. The finished actor reads a list of URLs, fetches each one, and pushes the page title to the dataset.

Use with an AI agent

Open this guide as a pre-filled prompt — or copy it for Claude Code, Cursor, Codex, or any other coding agent.

What you'll build

A minimal Apify actor in plain JavaScript that:

  • Takes a startUrls array as input.
  • Fetches each URL using built-in fetch.
  • Extracts the <title> of the page.
  • Pushes { url, title }to the run's dataset.

No Crawlee, no Puppeteer, no extra dependencies — just the Apify SDK and the standard library. Once it works, you'll know exactly where to plug in the more powerful tools.

Prerequisites

  • Node.js 18 or newer. Check with node --version. If you don't have it, grab the LTS build from nodejs.org.
  • An Apify account. The free tier is enough — sign up at console.apify.com.
  • A terminal. Any shell will do.

1. Install the Apify CLI and log in

The CLI is what scaffolds the project, runs it locally with the right env vars, and deploys it to the platform.

npm install -g apify-cli
apify --version
apify login

apify login opens a browser for OAuth and writes a token into ~/.apify/auth.json. After this the CLI can act on your behalf.

2. Scaffold the actor

apify create interactively walks you through picking a language and a template.

apify create my-first-actor
# Pick "JavaScript"  "Empty project"
cd my-first-actor
npm install

Pick JavaScript when asked, then the Empty project template — it gives you the smallest possible starting point.

Project structure

my-first-actor/
├── .actor/
│   ├── actor.json          # Actor metadata (name, version, build options).
│   └── input_schema.json   # Defines the input fields shown in the UI.
├── src/
│   └── main.js             # Your actor's entry point.
├── package.json
├── Dockerfile              # Used when the platform builds your actor.
└── README.md

Two files matter for now: src/main.js (the code) and .actor/input_schema.json (the inputs).

3. Define the input schema

The input schema does double duty: it powers the form in the Apify Console and validates input on every run. Replace the contents of .actor/input_schema.json with:

{
  "title": "Page Title Scraper input",
  "type": "object",
  "schemaVersion": 1,
  "properties": {
    "startUrls": {
      "title": "Start URLs",
      "type": "array",
      "description": "List of URLs to fetch the <title> of.",
      "editor": "requestListSources",
      "prefill": [
        { "url": "https://apify.com" },
        { "url": "https://news.ycombinator.com" }
      ]
    }
  },
  "required": ["startUrls"]
}

The prefill array gives the form a sensible default so users can hit Run without filling anything in.

4. Write the actor

Replace src/main.js with:

import { Actor } from 'apify';

await Actor.init();

const input = (await Actor.getInput()) ?? {};
const startUrls = input.startUrls ?? [{ url: 'https://apify.com' }];

for (const { url } of startUrls) {
  Actor.log.info(`Fetching ${url}`);

  const response = await fetch(url);
  const html = await response.text();

  const match = html.match(/<title[^>]*>([^<]+)<\/title>/i);
  const title = match ? match[1].trim() : '';

  await Actor.pushData({ url, title });
}

await Actor.exit();

A quick tour:

  • Actor.init() / Actor.exit() bracket your code so the SDK can flush logs, persist storage, and exit cleanly.
  • Actor.getInput() reads the JSON object provided on the Run screen (or apify_storage/key_value_stores/default/INPUT.json locally).
  • Actor.pushData()appends a row to the run's default dataset, which becomes the downloadable result.
  • Actor.log writes to the run log shown in the Console — use it instead of console.log so structured fields are preserved.

5. Run it locally

apify run --purge

The --purge flag wipes the local apify_storage/ directory before the run so you start with a clean slate. After it finishes you can inspect the output at apify_storage/datasets/default/.

6. Push it to the Apify platform

apify push

This zips your project, uploads it, and triggers a build. When the build finishes, your actor is live at console.apify.com/actors. The first run from the Console will use the input schema you defined.

Where to go next

  • Crawl more than a list of URLs. Swap the for loop for a Crawlee CheerioCrawler or PlaywrightCrawler and you get retries, concurrency, and a request queue for free.
  • Monetize. Set a Pay-Per-Result price on your actor and then add free-tier limits so non-paying users hit a ceiling.
  • Detect paying users. The APIFY_USER_IS_PAYING env var lets you ship richer output to paying users — see how to tell if a user is paying.
  • Price it. Run the Apify Pricing Calculator to translate your costs into the bundle string for your listing.

Spotted a bug, or want a guide on something else?

support@mail.apifyhub.com