cloudflare-cron-triggers — quality + safety report

In the Skillier index (secondsky__cloudflare-cron-triggers) · scanned 2026-06-03 · engine: builtin+triage

A
Quality
92/100
Safety

1 heuristic flag to review

Heuristic flags from the builtin scanner, which is known to over-flag (it trips on legitimate env-reading integrations, security skills, and library .eval calls). This is NOT an authoritative malicious verdict — re-scan with SkillSpector for the authoritative result. Run the authoritative scan →

Skillproof quality grade A

📇 This skill is in the Skillier index (curated · deduped · quality-filtered). Install Skillier to route & load it into your AI client.

Quality notes

Skill is large (~5377 tokens)
medium · quality · body
→ Tighten to the essential procedure; move long reference material to linked files.

About this skill

Cloudflare Cron Triggers for scheduled Workers execution. Use for periodic tasks, scheduled jobs, or encountering handler not found, invalid cron expression, timezone errors.

📄 Read the SKILL.md
---
name: cloudflare-cron-triggers
description: "Cloudflare Cron Triggers for scheduled Workers execution. Use for periodic tasks, scheduled jobs, or encountering handler not found, invalid cron expression, timezone errors."

metadata:
  keywords:
    - cloudflare cron
    - cron triggers
    - scheduled workers
    - scheduled handler
    - periodic tasks
    - background jobs
    - scheduled tasks
    - cron expression
    - wrangler crons
    - scheduled event
    - green compute
    - workflow triggers
    - maintenance tasks
    - scheduled() handler
    - ScheduledController
    - UTC timezone

license: MIT
---
# Cloudflare Cron Triggers

**Status**: Production Ready ✅
**Last Updated**: 2025-11-25
**Dependencies**: cloudflare-worker-base (for Worker setup)
**Latest Versions**: wrangler@4.50.0, @cloudflare/workers-types@4.20251125.0

---

## Quick Start (5 Minutes)

### 1. Add Scheduled Handler to Your Worker

**src/index.ts:**

```typescript
export default {
  async scheduled(
    controller: ScheduledController,
    env: Env,
    ctx: ExecutionContext
  ): Promise<void> {
    console.log('Cron job executed at:', new Date(controller.scheduledTime));
    console.log('Triggered by cron:', controller.cron);

    // Your scheduled task logic here
    await doPeriodicTask(env);
  },
};
```

**Why this matters:**
- Handler must be named exactly `scheduled` (not `scheduledHandler` or `onScheduled`)
- Must be exported in default export object
- Must use ES modules format (not Service Worker format)

### 2. Configure Cron Trigger in Wrangler

**wrangler.jsonc:**

```jsonc
{
  "name": "my-scheduled-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-10-23",
  "triggers": {
    "crons": [
      "0 * * * *"  // Every hour at minute 0
    ]
  }
}
```

**CRITICAL:**
- Cron expressions use 5 fields: `minute hour day-of-month month day-of-week`
- All times are **UTC only** (no timezone conversion)
- Changes take **up to 15 minutes** to propagate globally

### 3. Test Locally

```bash
# Enable scheduled testing
bunx wrangler dev --test-scheduled

# In another terminal, trigger the scheduled handler
curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*"

# View output in wrangler dev terminal
```

**Testing tips:**
- `/__scheduled` endpoint is only available with `--test-scheduled` flag
- Can pass any cron expression in query parameter
- Python Workers use `/cdn-cgi/handler/scheduled` instead

### 4. Deploy

```bash
npm run deploy
# or
bunx wrangler deploy
```

**After deployment:**
- Changes may take up to 15 minutes to propagate
- Check dashboard: Workers & Pages > [Your Worker] > **Cron Triggers**
- View past executions in **Logs** tab

---

## When to Load References

**Load immediately when user mentions**:

- `cron-expressions-reference.md` → "cron syntax", "schedule format", "expression", "minute hour day", "every X minutes"
- `common-patterns.md` → "examples", "use cases", "patterns", "real-world", "database cleanup", "report generation", "how to"
- `integration-patterns.md` → "implement", "Hono", "multiple triggers", "bindings", "workflows", "error handling"
- `wrangler-config.md` → "configuration", "wrangler.jsonc", "multiple crons", "environment-specific", "dev staging production"
- `testing-guide.md` → "test", "local development", "__scheduled", "unit test", "curl", "debugging"

**Load proactively when**:
- Building new scheduled task → Load `integration-patterns.md`
- Configuring wrangler.jsonc → Load `wrangler-config.md`
- Debugging cron expression → Load `cron-expressions-reference.md`
- Testing locally → Load `testing-guide.md`
- Looking for examples → Load `common-patterns.md`

---

## Cron Expression Syntax

### Five-Field Format

```
* * * * *
│ │ │ │ │
│ │ │ │ └─── Day of Week (0-6, Sunday=0)
│ │ │ └───── Month (1-12)
│ │ └─────── Day of Month (1-31)
│ └───────── Hour (0-23)
└─────────── Minute (0-59)
```

### Special Characters

| Character | Meaning | Example |
|-----------|---------|---------|
| `*` | Every | `* * * * *` = every minute |
| `,` | List | `0,30 * * * *` = every hour at :00 and :30 |
| `-` | Range | `0 9-17 * * *` = every hour from 9am-5pm |
| `/` | Step | `*/15 * * * *` = every 15 minutes |

### Common Patterns

```bash
# Every minute
* * * * *

# Every 5 minutes
*/5 * * * *

# Every 15 minutes
*/15 * * * *

# Every hour at minute 0
0 * * * *

# Every hour at minute 30
30 * * * *

# Every 6 hours
0 */6 * * *

# Every day at midnight (00:00 UTC)
0 0 * * *

# Every day at noon (12:00 UTC)
0 12 * * *

# Every day at 3:30am UTC
30 3 * * *

# Every Monday at 9am UTC
0 9 * * 1

# Every weekday at 9am UTC
0 9 * * 1-5

# Every Sunday at midnight UTC
0 0 * * 0

# First day of every month at midnight UTC
0 0 1 * *

# Twice a day (6am and 6pm UTC)
0 6,18 * * *

# Every 30 minutes during business hours (9am-5pm UTC, weekdays)
*/30 9-17 * * 1-5
```

**CRITICAL: UTC Timezone Only**
- All cron triggers execute on **UTC time**
- No timezone conversion available
- Convert your local time to UTC manually
- Example: 9am PST = 5pm UTC (next day during DST)

---

## ScheduledController Interface

```typescript
interface ScheduledController {
  readonly cron: string;           // The cron expression that triggered this execution
  readonly type: string;           // Always "scheduled"
  readonly scheduledTime: number;  // Unix timestamp (ms) when scheduled
}
```

### Properties

#### `controller.cron` (string)

The cron expression that triggered this execution.

```typescript
export default {
  async scheduled(controller: ScheduledController, env: Env): Promise<void> {
    console.log(`Triggered by: ${controller.cron}`);
    // Output: "Triggered by: 0 * * * *"
  },
};
```

**Use case:** Differentiate between multiple cron schedules (see Multiple Cron Triggers pattern).

#### `controller.type` (string)

Always returns `"scheduled"` for cron-triggered executions.

```typescript
if (controller.type === 'scheduled') {
  // This is a cron-triggered execution
}
```

#### `controller.scheduledTime` (number)

Unix timestamp (milliseconds since epoch) when this execution was scheduled to run.

```typescript
export default {
  async scheduled(controller: ScheduledController): Promise<void> {
    const scheduledDate = new Date(controller.scheduledTime);
    console.log(`Scheduled for: ${scheduledDate.toISOString()}`);
    // Output: "Scheduled for: 2025-10-23T15:00:00.000Z"
  },
};
```

**Note:** This is the **scheduled** time, not the actual execution time. Due to system load, actual execution may be slightly delayed (usually <1 second).

---

## Execution Context

```typescript
export default {
  async scheduled(
    controller: ScheduledController,
    env: Env,
    ctx: ExecutionContext  // ← Execution context
  ): Promise<void> {
    // Use ctx.waitUntil() for async operations that should complete
    ctx.waitUntil(logToAnalytics(env));
  },
};
```

### `ctx.waitUntil(promise: Promise<any>)`

Extends the execution context to wait for async operations to complete after the handler returns.

**Use cases:**
- Logging to external services
- Analytics tracking
- Cleanup operations
- Non-critical background tasks

```typescript
export default {
  async scheduled(controller: ScheduledController, env: Env, ctx: ExecutionContext): Promise<void> {
    // Critical task - must complete before handler exits
    await processData(env);

    // Non-critical tasks - can complete in background
    ctx.waitUntil(sendMetrics(env));
    ctx.waitUntil(cleanupOldData(env));
    ctx.waitUntil(notifySlack({ message: 'Cron completed' }));
  },
};
```

**Important:** First `waitUntil()` that fails will be reported as the status in dashboard logs.

---

## Integration Patterns

**6 production-ready cron patterns**:

1. **Standalone Worker with Cron** - Single scheduled function for background tasks (database cleanup, report generation)
2. **Hono + Cron Combination** - HTTP endpoints + scheduled tasks in one Worker, sharing bindings and reducing costs
3. **Multiple Cron Triggers** - Different schedules for different tasks using `controller.cron` to route execution
4. **Accessing Bindings** - Use D1, KV, R2, AI, Vectorize, Queues, Workflows, Durable Objects in scheduled functions
5. **Integrating with Workflows** - Trigger complex, long-running multi-step workflows on schedule
6. **Error Handling Best Practices** - Comprehensive error handling with retry logic, alerting (Slack/email), failure logging, and monitoring

**Load `references/integration-patterns.md` for complete implementations with code examples, configuration details, and best practices.**

---

## Wrangler Configuration

Add cron triggers to `wrangler.jsonc` in the `triggers.crons` array. Each trigger requires a `cron` expression. Supports multiple crons (Free: 3 max, Paid: higher limits) and environment-specific configurations for dev/staging/production deployments.

**Load `references/wrangler-config.md` for complete configuration examples including multiple triggers, environment-specific schedules, timezone handling, and removal procedures.**

---

## Testing & Development

Test scheduled functions locally using the `/__scheduled` endpoint by running `bunx wrangler dev --test-scheduled`, then triggering handlers with `curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*"` (use `+` instead of spaces in cron expressions).

**Load `references/testing-guide.md` for complete testing strategies, local development setup, unit testing examples, integration testing patterns, and production monitoring techniques.**

---

## Green Compute

Run cron triggers only in data centers powered by renewable energy.

### Enable Green Compute

**Via Dashboard:**

1. Go to [Workers & Pages](https://dash.cloudflare.com/?to=/:account/workers-and-pages)
2. In **Account details** section, find **Compute Setting**
3. Click **Change**
4. Select **Green Compute**
5. Click **Confirm**

**Applies to:**
- All cron triggers in your account
- Reduces carbon footprint
- No additional cost
- May introduce slight delays in some regions

**How it works:**
- Cloudflare routes cron executions to green-powered data centers
- Uses renewable energy: wind, solar, hydroelectric
- Verified through Power Purchase Agreements (PPAs) and Renewable Energy Credits (RECs)

---

## Known Issues Prevention

This skill prevents **6** documented issues:

### Issue #1: Cron Changes Not Propagating

**Error:** Cron triggers updated in wrangler.jsonc but not executing

**Source:** [Cloudflare Docs - Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/)

**Why It Happens:**
- Changes to cron triggers take up to **15 minutes** to propagate globally
- Cloudflare network needs time to update edge nodes
- No instant propagation like regular deploys

**Prevention:**
- Wait 15 minutes after deploy before expecting execution
- Check dashboard: Workers & Pages > [Worker] > Cron Triggers
- Use `wrangler triggers deploy` for trigger-only changes

```bash
# If you only changed triggers (not code), use:
bunx wrangler triggers deploy

# Wait 15 minutes, then verify in dashboard
```

---

### Issue #2: Handler Does Not Export

**Error:** `Handler does not export a 'scheduled' method`

**Source:** Common deployment error

**Why It Happens:**
- Handler not named exactly `scheduled`
- Handler not exported in default export object
- Using Service Worker format instead of ES modules

**Prevention:**

```typescript
// ❌ Wrong: Incorrect handler name
export default {
  async scheduledHandler(controller, env, ctx) { }
};

// ❌ Wrong: Not in default export
export async function scheduled(controller, env, ctx) { }

// ✅ Correct: Named 'scheduled' in default export
export default {
  async scheduled(controller, env, ctx) { }
};
```

---

### Issue #3: UTC Timezone Confusion

**Error:** Cron runs at wrong time

**Source:** User expectation vs. reality

**Why It Happens:**
- All cron triggers run on **UTC time only**
- No timezone conversion available
- Users expect local

… (truncated)
Scan or optimize your own skill →

Want a live grade + an embeddable README badge? Run your skill through the free scanner.

Graded independently by Skillproof — nothing to sell the author. Quality is mechanical + corpus-grounded; safety flags are heuristic (builtin+triage), not a malicious verdict.