RESOURCES · MINERAL HARVESTING

How to Harvest Minerals with an Extractor Safely

Find the room Mineral, require a same-tile owned active Extractor, check mineralAmount, Extractor cooldown, active WORK parts, Creep capacity and range, handle harvest() return codes, and verify Store and regeneration state on later ticks.

Verification statusChinese source article: Reviewed in full · Official docs: Checked — Mineral fields, StructureExtractor, harvest(), cooldown, regeneration and return codes · Structure boundary: The Extractor must be found on the Mineral tile; an arbitrary room Extractor is not accepted

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official docs
Checked — Mineral fields, StructureExtractor, harvest(), cooldown, regeneration and return codes
Structure boundary
The Extractor must be found on the Mineral tile; an arbitrary room Extractor is not accepted
Execution boundary
OK means the harvest action was scheduled; Creep Store, mineralAmount and cooldown require later observation
JavaScript syntax
Passed
Offline harvest review
Passed — Mineral, amount, same-tile Extractor, ownership, activity, cooldown, WORK, capacity and range states
Screeps Console test
Pending
Live Mineral depletion, regeneration, Store and cooldown test
Pending
Last verified
July 26, 2026

Quick answer

Get the current room Mineral, require mineralAmount > 0, find an owned active Extractor on the exact Mineral tile, wait for extractor.cooldown === 0, require an active WORK part and free Creep Store capacity, then call creep.harvest(mineral). Move only on ERR_NOT_IN_RANGE, and treat OK as a scheduled action that still needs later Store and cooldown verification.

Mineral harvesting requirements

  • The room Mineral object must currently be visible.
  • mineral.mineralAmount must be greater than zero.
  • An Extractor must be built on the Mineral coordinate.
  • The Extractor must be yours and active.
  • The Extractor cooldown must be zero.
  • The Creep needs at least one active WORK part.
  • The Creep needs free capacity for the Mineral type.
  • The Creep must be adjacent before the harvest can execute.

Mineral harvesting is not the same as early Source harvesting. The Extractor is an additional structure gate and becomes available at RCL 6.

Read the current Mineral state

function getRoomMineral(room) {
  if (!room) {
    return null;
  }

  return room.find(FIND_MINERALS)[0] || null;
}

Use the live object fields instead of maintaining a separate countdown:

function describeMineral(mineral) {
  if (!mineral) {
    return null;
  }

  return {
    id: mineral.id,
    mineralType: mineral.mineralType,
    mineralAmount: mineral.mineralAmount,
    ticksToRegeneration:
      mineral.ticksToRegeneration ?? null,
    x: mineral.pos.x,
    y: mineral.pos.y,
    roomName: mineral.pos.roomName
  };
}

ticksToRegeneration is meaningful during the depleted regeneration state. Do not decrement a copied Memory value and assume it remains synchronized with the object.

Find the Extractor on the Mineral tile

function findExtractorForMineral(room, mineral) {
  if (!room || !mineral) {
    return null;
  }

  const structures = room.lookForAt(
    LOOK_STRUCTURES,
    mineral.pos.x,
    mineral.pos.y
  );

  return structures.find(structure =>
    structure.structureType === STRUCTURE_EXTRACTOR
  ) || null;
}

A room-level find() result does not prove that the selected Extractor belongs to this Mineral. The coordinate lookup preserves the actual structure relationship.

Build a testable harvest plan

function evaluateMineralHarvest(input) {
  if (!input.mineralExists) {
    return { ready: false, reason: 'mineral-missing' };
  }

  if (
    !Number.isFinite(input.mineralAmount)
    || input.mineralAmount <= 0
  ) {
    return { ready: false, reason: 'mineral-depleted' };
  }

  if (!input.extractorExists || !input.extractorOnMineral) {
    return { ready: false, reason: 'extractor-missing' };
  }

  if (!input.extractorOwned || !input.extractorActive) {
    return { ready: false, reason: 'extractor-inactive' };
  }

  if (
    !Number.isInteger(input.extractorCooldown)
    || input.extractorCooldown > 0
  ) {
    return { ready: false, reason: 'extractor-not-ready' };
  }

  if (
    !Number.isInteger(input.activeWorkParts)
    || input.activeWorkParts <= 0
  ) {
    return { ready: false, reason: 'no-active-work-part' };
  }

  if (
    !Number.isFinite(input.freeCapacity)
    || input.freeCapacity <= 0
  ) {
    return { ready: false, reason: 'creep-full' };
  }

  if (!input.isNearMineral) {
    return { ready: false, reason: 'move-to-mineral' };
  }

  return { ready: true, reason: 'ready' };
}

This pure plan makes local branches testable, but the official action return code remains the same-tick authority.

Complete Mineral harvester example

function runMineralHarvester(creep) {
  if (!creep || creep.spawning === true) {
    return { status: 'creep-unavailable' };
  }

  const mineral = getRoomMineral(creep.room);
  if (!mineral) {
    return { status: 'mineral-missing' };
  }

  if (mineral.mineralAmount <= 0) {
    return {
      status: 'mineral-depleted',
      ticksToRegeneration:
        mineral.ticksToRegeneration ?? null
    };
  }

  const extractor = findExtractorForMineral(
    creep.room,
    mineral
  );
  if (!extractor) {
    return { status: 'extractor-missing' };
  }

  const plan = evaluateMineralHarvest({
    mineralExists: true,
    mineralAmount: mineral.mineralAmount,
    extractorExists: true,
    extractorOnMineral:
      extractor.pos.isEqualTo(mineral.pos),
    extractorOwned: extractor.my === true,
    extractorActive: extractor.isActive() === true,
    extractorCooldown: extractor.cooldown,
    activeWorkParts:
      creep.getActiveBodyparts(WORK),
    freeCapacity: creep.store.getFreeCapacity(
      mineral.mineralType
    ),
    isNearMineral: creep.pos.isNearTo(mineral.pos)
  });

  if (plan.reason === 'move-to-mineral') {
    return {
      status: 'moving-to-mineral',
      result: creep.moveTo(mineral, {
        range: 1,
        reusePath: 10
      })
    };
  }

  if (!plan.ready) {
    return {
      status: plan.reason,
      mineralId: mineral.id,
      extractorId: extractor.id
    };
  }

  const before = {
    gameTick: Game.time,
    mineralAmount: mineral.mineralAmount,
    creepAmount: creep.store.getUsedCapacity(
      mineral.mineralType
    ),
    extractorCooldown: extractor.cooldown
  };
  const result = creep.harvest(mineral);

  return {
    status: result === OK
      ? 'harvest-scheduled'
      : 'harvest-rejected',
    result,
    mineralId: mineral.id,
    mineralType: mineral.mineralType,
    extractorId: extractor.id,
    before
  };
}
module.exports.loop = function () {
  const creep = Game.creeps.Miner1;
  if (!creep) {
    return;
  }

  const outcome = runMineralHarvester(creep);
  if (
    outcome.status === 'harvest-rejected'
    || Game.time % 100 === 0
  ) {
    console.log(JSON.stringify({
      type: 'mineral-harvester-status',
      creepName: creep.name,
      ...outcome
    }));
  }
};

Respect Extractor cooldown

A successful Mineral harvest places the Extractor into cooldown. Reissuing harvest() while the cooldown is positive only produces repeated ERR_TIRED results. Read the structure field each tick and let the worker wait, deliver, or perform another explicit task.

function extractorReady(extractor) {
  return Boolean(
    extractor
    && extractor.my === true
    && extractor.isActive() === true
    && extractor.cooldown === 0
  );
}

Handle depletion and regeneration

When mineralAmount reaches zero, stop assigning harvest actions. Store the current observation for diagnostics, but use the live ticksToRegeneration field rather than treating an old Memory countdown as truth.

function mineralAvailability(mineral) {
  if (!mineral) {
    return { available: false, reason: 'missing' };
  }

  if (mineral.mineralAmount <= 0) {
    return {
      available: false,
      reason: 'regenerating',
      ticksToRegeneration:
        mineral.ticksToRegeneration ?? null
    };
  }

  return {
    available: true,
    reason: 'available',
    mineralAmount: mineral.mineralAmount
  };
}

Verify the next tick

Save the Mineral ID, Extractor ID, Creep name, mineral type, Store amount and game tick. On a later tick, recover the objects and compare:

  • Creep Store for the Mineral type;
  • mineral.mineralAmount;
  • extractor.cooldown;
  • the original action return code.

Other Creep actions or dropped resources can affect totals, so describe the observed deltas rather than claiming causality from one number alone.

Handle return codes

CodeMeaning hereReview
OKHarvest scheduledStore, amount and cooldown later
ERR_BUSYCreep still spawningcreep.spawning
ERR_NOT_FOUNDNo Extractor on the Mineral tileSame-tile lookup
ERR_NOT_ENOUGH_RESOURCESMineral depletedmineralAmount and regeneration
ERR_INVALID_TARGETTarget is not harvestableObject type and visibility
ERR_NOT_IN_RANGECreep not adjacentMove to range 1
ERR_TIREDExtractor cooling downextractor.cooldown
ERR_NO_BODYPARTNo active WORK partCurrent body damage

Debugging checklist

  • Confirm the room and Mineral are visible.
  • Read current mineralAmount.
  • Find structures at the Mineral coordinates.
  • Confirm Extractor ownership and activity.
  • Wait for cooldown zero.
  • Count active WORK parts.
  • Check capacity for the exact Mineral type.
  • Move only when out of range.
  • Save the action result and before snapshot.
  • Verify Store, amount and cooldown later.

Scope and next steps

This guide does not design the Miner body, haul minerals, schedule regeneration, operate Labs, value minerals, or sell them. Continue with Storage Energy logistics and the existing Lab guides.

Frequently asked questions

Can a Creep harvest without CARRY?

The action may still produce resources, but without available Store capacity they can drop on the ground. This example requires capacity to keep the logistics explicit.

Why not hard-code the regeneration time?

The live object exposes the current remaining regeneration state. Reading it avoids drift between Memory and the game object.

Should multiple WORK parts ignore cooldown?

No. More active WORK parts can increase one harvest amount, but the Extractor still controls how often Mineral harvesting can occur.

Official documentation

SOURCE AND SCOPE

Review the source, evidence, or next system

This English guide is rewritten for a focused search intent while preserving the technical scope and verification boundaries of its Chinese source. Live-room evidence is claimed only when the verification record says it exists.