Skip to main content

Building an Apify Actor with TypeScript 2026: Complete Tutorial

· 4 min read
Yassine El Haddad
Software Developer & Automation Specialist

I build production AI agents, web scrapers, and automation pipelines. Most of what I publish here comes from the actual problems they run into: proxies that get banned, anti-bot stacks that fingerprint your client, RAG that drifts when the underlying data moves. Stack: Python, TypeScript, Go, FastAPI, LangChain, Crawlee, Playwright, deployed on AWS, GCP, and Cloudflare.

Build an Apify Actor from scratch with TypeScript, Crawlee, and the Apify SDK. Scaffold with apify create, choose Cheerio or Playwright, define input, run locally, then push to the cloud. Start building with a free Apify account.

Prerequisites

Step 1: Create the project

apify create my-typescript-actor

Select Crawlee + Playwright (TypeScript) or Crawlee + Cheerio (TypeScript). Cheerio is faster for static HTML; Playwright handles JavaScript-heavy pages.

This creates:

my-typescript-actor/
├── .actor/
│ ├── actor.json
│ └── input_schema.json
├── src/
│ ├── main.ts
│ └── routes.ts # Playwright only
├── package.json
├── tsconfig.json
└── Dockerfile

Step 2: Actor structure

main.ts — entry point. Initialize Apify, read input, run crawler, exit:

import { Actor } from 'apify';
import { CheerioCrawler } from 'crawlee';

await Actor.init();

interface Input {
startUrls: { url: string }[];
maxRequestsPerCrawl?: number;
}

const input = (await Actor.getInput<Input>()) ?? {};
const { startUrls = [{ url: 'https://example.com' }], maxRequestsPerCrawl = 50 } = input;

const proxyConfiguration = await Actor.createProxyConfiguration();

const crawler = new CheerioCrawler({
proxyConfiguration,
maxRequestsPerCrawl,
async requestHandler({ $, request }) {
const title = $('title').text();
await Actor.pushData({ url: request.loadedUrl, title });
},
});

await crawler.run(startUrls);
await Actor.exit();

INPUT_SCHEMA.json (or input_schema.json) — defines the input form. See the input schema guide for details:

{
"title": "My Actor Input",
"type": "object",
"schemaVersion": 1,
"properties": {
"startUrls": {
"title": "Start URLs",
"type": "array",
"editor": "requestListSources",
"prefill": [{ "url": "https://example.com" }]
},
"maxRequestsPerCrawl": {
"title": "Max requests",
"type": "integer",
"default": 50,
"minimum": 1,
"maximum": 1000
}
},
"required": ["startUrls"]
}

Step 3: CheerioCrawler vs PlaywrightCrawler

CrawlerUse whenSpeedJS support
CheerioCrawlerStatic HTML, fastFastNo
PlaywrightCrawlerSPAs, login, dynamic contentSlowerYes

CheerioCrawler example:

import { CheerioCrawler } from 'crawlee';

const crawler = new CheerioCrawler({
async requestHandler({ $, request }) {
const links = $('a[href]').map((_, el) => $(el).attr('href')).get();
await Actor.pushData({ url: request.url, links });
},
});

PlaywrightCrawler example:

import { PlaywrightCrawler } from 'crawlee';

const crawler = new PlaywrightCrawler({
async requestHandler({ page, request }) {
await page.waitForSelector('.product-title');
const title = await page.locator('.product-title').first().textContent();
await Actor.pushData({ url: request.url, title });
},
});

Step 4: Storing results

Use Actor.pushData() to append rows to the default dataset. Data appears in the Storage tab after the run.

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

For key-value output (single JSON):

await Actor.setValue('OUTPUT', { summary: { total: 42 } });

Step 5: Local testing

cd my-typescript-actor
apify login # if not already
apify run

Input comes from .actor/actor.json defaultInput or --input:

apify run --input '{"startUrls":[{"url":"https://crawlee.dev"}],"maxRequestsPerCrawl":5}'

Output is written to storage/datasets/default/. Check storage/datasets/default/*.json for scraped items.

Step 6: Push to Apify

apify push

This builds the Docker image and uploads the Actor. Run it from the Apify Console, schedule it, or call the API.

Summary

  1. apify create — scaffold a TypeScript Actor
  2. main.tsActor.init(), getInput(), crawler, Actor.exit()
  3. INPUT_SCHEMA — define startUrls, limits, proxy
  4. CheerioCrawler for static HTML, PlaywrightCrawler for JS sites
  5. Actor.pushData() — store results in the dataset
  6. apify run — test locally
  7. apify push — deploy to Apify Cloud

For testing, input schema design, and proxy setup, see the related guides above.

Apify Affiliate Banner 728x90Apify Affiliate Banner 728x90Apify Affiliate Banner 300x50Apify Affiliate Banner 300x50
Next step

Create your first Actor with apify create, run it locally, then push. Deploy to Apify →

Frequently Asked Questions

Yes. Apify CLI templates include TypeScript (Crawlee + Playwright, Crawlee + Cheerio). Use apify create and select the TypeScript option.

Run apify create, pick a TypeScript template, edit src/main.ts and .actor/input_schema.json, then apify run and apify push.

TypeScript adds type safety and better IDE support. Both work. Apify and Crawlee support both with full type definitions.

Actor.pushData() writes to the default dataset. Access it via the Storage tab in Console or the Apify API after the run finishes.

Common mistakes and fixes

apify create not finding TypeScript template

Run apify create, then choose 'Crawlee + Playwright (TypeScript)' or 'Crawlee + Cheerio (TypeScript)' from the template list.

Actor.exit() not called causes hung run

Always call await Actor.exit() at the end. Wrap logic in try/finally to ensure exit runs even on errors.