DEV Community

Alex Kane
Alex Kane

Posted on

n8n for Agriculture & AgTech: 5 Automations That Reduce Labor and Boost Yields (Free Workflow JSON)

Agricultural operations run on tight margins and tight schedules. A delayed irrigation trigger, a missed equipment service, or a missed commodity price spike can cost thousands. Yet most farm management still relies on manual checks, spreadsheets, and phone calls.

n8n changes that. It's open-source, self-hostable, and connects 400+ services — including IoT platforms, USDA APIs, GPS telematics, and your existing spreadsheets. No per-task pricing. Your field data stays on your infrastructure.

Here are 5 production-ready n8n automations for agriculture and AgTech teams, with full import-ready JSON for each.


1. Soil Sensor Alert & Irrigation Trigger

The problem: Crop stress from under- or over-watering costs yield and water — but manually checking soil sensors across multiple zones is impractical.

The workflow:

  • Schedule: Every 30 minutes
  • HTTP Request: Poll each zone's IoT sensor API for soil moisture reading
  • Code node: Compare reading vs. field-specific threshold (e.g., <30% VWC = dry)
  • IF: If dry → Slack alert to #field-ops + HTTP Request to irrigation controller API to activate zone
  • Sheets: Log reading, timestamp, action taken
{
  "name": "Soil Sensor Alert & Irrigation Trigger",
  "nodes": [
    {
      "parameters": {
        "rule": {"interval": [{"field": "minutes", "minutesInterval": 30}]}
      },
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [240, 300]
    },
    {
      "parameters": {
        "url": "={{$vars.SENSOR_API_URL}}/zones",
        "authentication": "headerAuth",
        "options": {}
      },
      "name": "Get Sensor Readings",
      "type": "n8n-nodes-base.httpRequest",
      "position": [460, 300]
    },
    {
      "parameters": {
        "jsCode": "const zones = $input.all().map(i => i.json);\nconst DRY_THRESHOLD = 30;\nreturn zones.map(z => ({\n  json: {\n    zone_id: z.zone_id,\n    zone_name: z.name,\n    moisture_pct: z.soil_moisture_pct,\n    is_dry: z.soil_moisture_pct < DRY_THRESHOLD,\n    reading_ts: new Date().toISOString()\n  }\n}));"
      },
      "name": "Evaluate Moisture",
      "type": "n8n-nodes-base.code",
      "position": [680, 300]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [{"value1": "={{$json.is_dry}}", "value2": true}]
        }
      },
      "name": "IF Dry",
      "type": "n8n-nodes-base.if",
      "position": [900, 300]
    },
    {
      "parameters": {
        "url": "={{$vars.IRRIGATION_API_URL}}/zones/={{$json.zone_id}}/activate",
        "requestMethod": "POST",
        "body": {"duration_minutes": 20}
      },
      "name": "Activate Irrigation",
      "type": "n8n-nodes-base.httpRequest",
      "position": [1120, 200]
    },
    {
      "parameters": {
        "channel": "#field-ops",
        "text": "🌱 Zone *{{$json.zone_name}}* is DRY ({{$json.moisture_pct}}% VWC). Irrigation activated for 20 min.",
        "otherOptions": {}
      },
      "name": "Slack Alert",
      "type": "n8n-nodes-base.slack",
      "position": [1120, 360]
    }
  ],
  "connections": {
    "Schedule Trigger": {"main": [[{"node": "Get Sensor Readings", "type": "main", "index": 0}]]},
    "Get Sensor Readings": {"main": [[{"node": "Evaluate Moisture", "type": "main", "index": 0}]]},
    "Evaluate Moisture": {"main": [[{"node": "IF Dry", "type": "main", "index": 0}]]},
    "IF Dry": {"main": [[{"node": "Activate Irrigation", "type": "main", "index": 0}], [{"node": "Slack Alert", "type": "main", "index": 0}]]}
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Harvest Schedule & Crew Coordination Notifier

The problem: Harvest windows are narrow — a 2-day weather window for wheat or a 3-day peak for tomatoes. Coordinating crews, equipment, and logistics manually across multiple fields burns time you don't have.

The workflow:

  • Schedule: Daily at 6 AM
  • Sheets: Read crop calendar (field, crop, expected harvest date, crew lead, equipment needed)
  • Code: Filter rows where harvest_date is within the next 7 days; calculate days remaining
  • Gmail: Send personalized reminder to each field manager with their assigned crops and crew instructions
  • Slack: Post daily harvest countdown summary to #harvest-crew
{
  "name": "Harvest Schedule & Crew Notifier",
  "nodes": [
    {
      "parameters": {
        "rule": {"interval": [{"field": "hours", "hoursInterval": 24, "triggerAtHour": 6}]}
      },
      "name": "Daily 6AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [240, 300]
    },
    {
      "parameters": {
        "operation": "read",
        "sheetName": "CropCalendar"
      },
      "name": "Read Crop Calendar",
      "type": "n8n-nodes-base.googleSheets",
      "position": [460, 300]
    },
    {
      "parameters": {
        "jsCode": "const today = new Date();\nreturn $input.all()\n  .filter(item => {\n    const harvestDate = new Date(item.json.harvest_date);\n    const daysLeft = Math.ceil((harvestDate - today) / 86400000);\n    return daysLeft >= 0 && daysLeft <= 7;\n  })\n  .map(item => {\n    const harvestDate = new Date(item.json.harvest_date);\n    const daysLeft = Math.ceil((harvestDate - today) / 86400000);\n    return { json: { ...item.json, days_left: daysLeft,\n      urgency: daysLeft <= 2 ? 'CRITICAL' : daysLeft <= 4 ? 'URGENT' : 'UPCOMING' } };\n  });"
      },
      "name": "Filter & Classify",
      "type": "n8n-nodes-base.code",
      "position": [680, 300]
    },
    {
      "parameters": {
        "fromEmail": "ops@farmhq.com",
        "toEmail": "={{$json.manager_email}}",
        "subject": "[{{$json.urgency}}] Harvest in {{$json.days_left}} day(s): {{$json.field_name}} — {{$json.crop}}",
        "text": "Field: {{$json.field_name}}\nCrop: {{$json.crop}}\nHarvest Date: {{$json.harvest_date}} ({{$json.days_left}} day(s))\nCrew Lead: {{$json.crew_lead}}\nEquipment: {{$json.equipment_needed}}\n\nPlease confirm crew availability and equipment readiness."
      },
      "name": "Email Field Manager",
      "type": "n8n-nodes-base.gmail",
      "position": [900, 200]
    },
    {
      "parameters": {
        "channel": "#harvest-crew",
        "text": "*Harvest Alert* — {{$json.urgency}}: {{$json.field_name}} ({{$json.crop}}) in *{{$json.days_left}} day(s)*. Lead: {{$json.crew_lead}} | Equipment: {{$json.equipment_needed}}",
        "otherOptions": {}
      },
      "name": "Slack Crew Alert",
      "type": "n8n-nodes-base.slack",
      "position": [900, 400]
    }
  ],
  "connections": {
    "Daily 6AM": {"main": [[{"node": "Read Crop Calendar", "type": "main", "index": 0}]]},
    "Read Crop Calendar": {"main": [[{"node": "Filter & Classify", "type": "main", "index": 0}]]},
    "Filter & Classify": {"main": [[{"node": "Email Field Manager", "type": "main", "index": 0}], [{"node": "Slack Crew Alert", "type": "main", "index": 0}]]}
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Commodity Price Monitor & Alert

The problem: Corn, wheat, soybean, and livestock prices move fast. Missing a 5% intraday spike — or a contract-trigger price — can mean the difference between locking in margin and leaving money on the table.

The workflow:

  • Schedule: Daily at 7 AM (or every 2 hours during market hours)
  • HTTP Request: Fetch commodity prices from USDA AMS API or CME Group feed
  • Code: Compare today's price vs. yesterday's; calculate delta %; check if any commodity hit a target price
  • IF: If price change > 2% or target hit → Slack alert to #management + email to farm owner
  • Sheets: Append price history for trend tracking
{
  "name": "Commodity Price Monitor",
  "nodes": [
    {
      "parameters": {
        "rule": {"interval": [{"field": "hours", "hoursInterval": 24, "triggerAtHour": 7}]}
      },
      "name": "Daily 7AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [240, 300]
    },
    {
      "parameters": {
        "url": "https://marsapi.ams.usda.gov/services/v1.2/reports/2193",
        "options": {"headers": {"API_KEY": "={{$vars.USDA_API_KEY}}"}}
      },
      "name": "Fetch Commodity Prices",
      "type": "n8n-nodes-base.httpRequest",
      "position": [460, 300]
    },
    {
      "parameters": {
        "jsCode": "const prices = $input.first().json.results || [];\nconst TARGET_PRICES = { CORN: 4.80, WHEAT: 6.20, SOYBEANS: 11.50 };\nreturn prices.map(p => {\n  const prev = parseFloat(p.prev_price || p.price);\n  const curr = parseFloat(p.price);\n  const delta_pct = ((curr - prev) / prev * 100).toFixed(2);\n  const target = TARGET_PRICES[p.commodity];\n  return { json: {\n    commodity: p.commodity,\n    price: curr,\n    prev_price: prev,\n    delta_pct: parseFloat(delta_pct),\n    target_hit: target ? curr >= target : false,\n    alert: Math.abs(delta_pct) > 2 || (target && curr >= target)\n  }};\n}).filter(i => i.json.alert);"
      },
      "name": "Calculate Alerts",
      "type": "n8n-nodes-base.code",
      "position": [680, 300]
    },
    {
      "parameters": {
        "channel": "#management",
        "text": "📈 *Commodity Alert* — {{$json.commodity}}: ${{$json.price}} ({{$json.delta_pct > 0 ? '+' : ''}}{{$json.delta_pct}}% vs yesterday){{$json.target_hit ? ' 🎯 TARGET HIT' : ''}}",
        "otherOptions": {}
      },
      "name": "Slack Price Alert",
      "type": "n8n-nodes-base.slack",
      "position": [900, 300]
    }
  ],
  "connections": {
    "Daily 7AM": {"main": [[{"node": "Fetch Commodity Prices", "type": "main", "index": 0}]]},
    "Fetch Commodity Prices": {"main": [[{"node": "Calculate Alerts", "type": "main", "index": 0}]]},
    "Calculate Alerts": {"main": [[{"node": "Slack Price Alert", "type": "main", "index": 0}]]}
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Equipment Maintenance Tracker & Alert

The problem: A tractor breakdown during harvest is a catastrophic event. Most farm equipment has service intervals measured in engine hours or calendar time — but tracking that manually across 10–20 pieces of equipment means something always slips.

The workflow:

  • Schedule: Daily at 6 AM
  • Sheets: Read equipment inventory (machine, last service date, service interval, telematics hours)
  • Code: Calculate days/hours since last service; compare vs. interval; classify OVERDUE / DUE_SOON / OK
  • Gmail: Email mechanic with list of equipment due or overdue for service
  • Slack: Alert #equipment channel
{
  "name": "Equipment Maintenance Tracker",
  "nodes": [
    {
      "parameters": {
        "rule": {"interval": [{"field": "hours", "hoursInterval": 24, "triggerAtHour": 6}]}
      },
      "name": "Daily 6AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [240, 300]
    },
    {
      "parameters": {
        "operation": "read",
        "sheetName": "Equipment"
      },
      "name": "Read Equipment Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [460, 300]
    },
    {
      "parameters": {
        "jsCode": "const today = new Date();\nreturn $input.all().map(item => {\n  const e = item.json;\n  const lastService = new Date(e.last_service_date);\n  const daysSince = Math.floor((today - lastService) / 86400000);\n  const intervalDays = parseInt(e.service_interval_days || 90);\n  const daysUntil = intervalDays - daysSince;\n  let status;\n  if (daysUntil < 0) status = 'OVERDUE';\n  else if (daysUntil <= 7) status = 'DUE_THIS_WEEK';\n  else if (daysUntil <= 14) status = 'DUE_SOON';\n  else status = 'OK';\n  return { json: { ...e, days_since_service: daysSince, days_until_service: daysUntil, status } };\n}).filter(i => i.json.status !== 'OK');"
      },
      "name": "Calculate Service Status",
      "type": "n8n-nodes-base.code",
      "position": [680, 300]
    },
    {
      "parameters": {
        "fromEmail": "ops@farmhq.com",
        "toEmail": "mechanic@farmhq.com",
        "subject": "[{{$json.status}}] Equipment Service Due: {{$json.equipment_name}}",
        "text": "Equipment: {{$json.equipment_name}}\nStatus: {{$json.status}}\nLast Service: {{$json.last_service_date}} ({{$json.days_since_service}} days ago)\nInterval: Every {{$json.service_interval_days}} days\nDays Until/Overdue: {{$json.days_until_service}}\n\nPlease schedule service as soon as possible."
      },
      "name": "Email Mechanic",
      "type": "n8n-nodes-base.gmail",
      "position": [900, 200]
    },
    {
      "parameters": {
        "channel": "#equipment",
        "text": "🔧 *{{$json.status}}* — {{$json.equipment_name}}: last serviced {{$json.days_since_service}}d ago (interval: {{$json.service_interval_days}}d). Days until/overdue: {{$json.days_until_service}}",
        "otherOptions": {}
      },
      "name": "Slack Equipment Alert",
      "type": "n8n-nodes-base.slack",
      "position": [900, 400]
    }
  ],
  "connections": {
    "Daily 6AM": {"main": [[{"node": "Read Equipment Sheet", "type": "main", "index": 0}]]},
    "Read Equipment Sheet": {"main": [[{"node": "Calculate Service Status", "type": "main", "index": 0}]]},
    "Calculate Service Status": {"main": [[{"node": "Email Mechanic", "type": "main", "index": 0}], [{"node": "Slack Equipment Alert", "type": "main", "index": 0}]]}
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Weekly Farm Operations Report

The problem: Farm owners need a weekly pulse — yield progress, water usage, labor hours, equipment downtime, and revenue outlook — but generating it manually from multiple spreadsheets takes hours.

The workflow:

  • Schedule: Every Friday at 5 PM
  • Sheets: Read yield data, irrigation log, labor hours, equipment downtime log
  • Code: Calculate key KPIs — bushels/acre vs. target, irrigation cost/acre, labor cost/acre, projected harvest revenue vs. expense
  • Gmail: Send formatted HTML report to farm owner (+ agronomist BCC)
{
  "name": "Weekly Farm Operations Report",
  "nodes": [
    {
      "parameters": {
        "rule": {"interval": [{"field": "cronExpression", "expression": "0 17 * * 5"}]}
      },
      "name": "Friday 5PM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [240, 300]
    },
    {
      "parameters": {"operation": "read", "sheetName": "YieldData"},
      "name": "Yield Data",
      "type": "n8n-nodes-base.googleSheets",
      "position": [460, 200]
    },
    {
      "parameters": {"operation": "read", "sheetName": "IrrigationLog"},
      "name": "Irrigation Log",
      "type": "n8n-nodes-base.googleSheets",
      "position": [460, 400]
    },
    {
      "parameters": {
        "mode": "multiplex"
      },
      "name": "Merge Data",
      "type": "n8n-nodes-base.merge",
      "position": [700, 300]
    },
    {
      "parameters": {
        "jsCode": "const yieldData = $input.all().filter(i => i.json.source === 'yield');\nconst irrigData = $input.all().filter(i => i.json.source === 'irrigation');\n\nconst totalAcres = yieldData.reduce((s, i) => s + parseFloat(i.json.acres || 0), 0);\nconst totalBushels = yieldData.reduce((s, i) => s + parseFloat(i.json.bushels_harvested || 0), 0);\nconst buPerAcre = totalAcres > 0 ? (totalBushels / totalAcres).toFixed(1) : 0;\nconst totalWaterGal = irrigData.reduce((s, i) => s + parseFloat(i.json.gallons_applied || 0), 0);\nconst waterCostPerAcre = totalAcres > 0 ? ((totalWaterGal * 0.001) / totalAcres).toFixed(2) : 0;\n\nconst html = `<h2>Weekly Farm Operations Report</h2>\n<table border='1' cellpadding='8'>\n<tr><th>Metric</th><th>This Week</th></tr>\n<tr><td>Acres in Production</td><td>${totalAcres}</td></tr>\n<tr><td>Bushels Harvested</td><td>${totalBushels.toLocaleString()}</td></tr>\n<tr><td>Avg Yield (bu/acre)</td><td>${buPerAcre}</td></tr>\n<tr><td>Water Applied (gal)</td><td>${totalWaterGal.toLocaleString()}</td></tr>\n<tr><td>Water Cost/Acre</td><td>$${waterCostPerAcre}</td></tr>\n</table>`;\n\nreturn [{ json: { html, buPerAcre, totalAcres, totalBushels, totalWaterGal, waterCostPerAcre } }];"
      },
      "name": "Build Report",
      "type": "n8n-nodes-base.code",
      "position": [920, 300]
    },
    {
      "parameters": {
        "fromEmail": "ops@farmhq.com",
        "toEmail": "owner@farmhq.com",
        "subject": "Weekly Farm Report — {{new Date().toLocaleDateString()}}",
        "html": "={{$json.html}}"
      },
      "name": "Email Report",
      "type": "n8n-nodes-base.gmail",
      "position": [1140, 300]
    }
  ],
  "connections": {
    "Friday 5PM": {"main": [[{"node": "Yield Data", "type": "main", "index": 0}]]},
    "Yield Data": {"main": [[{"node": "Merge Data", "type": "main", "index": 0}]]},
    "Irrigation Log": {"main": [[{"node": "Merge Data", "type": "main", "index": 1}]]},
    "Merge Data": {"main": [[{"node": "Build Report", "type": "main", "index": 0}]]},
    "Build Report": {"main": [[{"node": "Email Report", "type": "main", "index": 0}]]}
  }
}
Enter fullscreen mode Exit fullscreen mode

Why n8n fits agriculture better than Zapier or Make.com

Feature n8n Zapier Make.com
Self-hosted (field data stays on-site)
Offline-capable edge deployment
Cost at 50,000+ ops/month ~$0 (self-hosted) $299–999+/mo $99–299+/mo
Custom IoT integrations (sensors, SCADA) ✅ HTTP Request node Limited Limited
Git-versioned workflow JSON
Runs on Raspberry Pi / edge hardware

Data sovereignty matters here. Field maps, soil data, crop yields, commodity positions, and equipment telemetry are commercially sensitive — and often gathered in areas with unreliable internet. Self-hosted n8n on a local server (or a $35 Raspberry Pi 5) keeps everything on your network and keeps running during connectivity gaps.


Get the templates

All 5 workflows above are available as import-ready JSON in the FlowKit n8n Automation Template Library at stripeai.gumroad.com.

The library includes 15 workflows across automation categories — each ready to import into your n8n instance, configure your credentials, and run.

Have questions or need a custom workflow? Drop a comment below.


Tags: n8n, agriculture, agtech, automation, iot

Top comments (0)