Quick answer
To harvest Energy, pick an active Source, call creep.harvest(source), and move toward it when the result is ERR_NOT_IN_RANGE. When the Creep has no free Energy capacity left, stop the harvesting phase and move on to delivery.
The important beginner distinction is simple: Store capacity is Creep state; the value returned by harvest() is an action result. Do not wait for ERR_FULL to tell you the Store is full. Source harvesting does not return ERR_FULL.
Start with this minimal harvesting loop
Pass one of your Creeps into this function. It does not assume a particular Creep name, room name, or Source ID:
function harvestEnergy(creep) {
const freeEnergy =
creep.store.getFreeCapacity(RESOURCE_ENERGY);
if (freeEnergy === null || freeEnergy === 0) {
return { status: 'ready-for-delivery' };
}
const source =
creep.pos.findClosestByPath(FIND_SOURCES_ACTIVE);
if (!source) {
return { status: 'no-active-source' };
}
const harvestResult = creep.harvest(source);
let moveResult = null;
if (harvestResult === ERR_NOT_IN_RANGE) {
moveResult = creep.moveTo(source, {
range: 1,
reusePath: 5
});
}
return {
status: harvestResult === OK
? 'harvest-accepted'
: 'harvest-not-accepted',
sourceId: source.id,
harvestResult,
moveResult
};
}
A normal first trip is easy to read: while the Store has room, find an active Source; if the Source is too far away, move; once adjacent, harvest() can return OK; when the Store is full, leave this acquisition function and run the delivery step.
What happens each tick
- Read Store state.
getFreeCapacity(RESOURCE_ENERGY)decides whether this role still needs to acquire Energy. - Choose a usable Source.
FIND_SOURCES_ACTIVEavoids choosing a currently empty Source in this beginner loop. - Submit the harvest action. If the Creep is adjacent and the other API conditions are satisfied,
harvest()returnsOK. - Handle range separately. If harvesting returns
ERR_NOT_IN_RANGE, submit movement and keep the movement result separate from the harvest result. - Run the loop again next tick. Screeps processes intents after your player code, so
OKis not the same thing as a later observed Store increase.
This separation makes debugging much easier. You can tell whether the problem is current state, the harvest call, or movement instead of collapsing all three into one boolean.
When should the Creep stop harvesting?
For a first Creep that harvests and then carries Energy to another object, the simplest phase boundary is:
Free Energy capacity available → harvest. No free Energy capacity → deliver.
That rule is enough to understand the basic loop. Once ready-for-delivery is returned, hand the Creep to your delivery logic instead of continuing to call harvest().
This is application state, not a special harvest return code. In particular, do not write role logic that waits for creep.harvest(source) === ERR_FULL. The current Source-harvest API path has no Store-capacity ERR_FULL preflight.
Source-specific Creep.harvest() return codes
This lesson is scoped to Source Energy. Minerals and Deposits have additional conditions, so keep their branches out of a first Source-harvesting loop.
| Result | What it means here | Next step |
|---|---|---|
OK | The harvest intent was accepted for this tick. | Check the later visible Source and Store state if you need proof of the processed result. |
ERR_NOT_IN_RANGE | The Creep is not adjacent to the Source. | Move to range 1, then retry on a later tick. |
ERR_NOT_ENOUGH_RESOURCES | The Source currently has no Energy. | Wait for regeneration or choose another active Source. |
ERR_NO_BODYPART | No active WORK part is available. | Inspect the Creep body and damage. |
ERR_BUSY | The Creep is still spawning. | Wait until spawning is complete. |
ERR_INVALID_TARGET | The object passed to this call is not a valid harvest target. | Resolve the Source again. |
ERR_NOT_OWNER | The actor or room ownership/reservation boundary does not allow this Source harvest. | Check Creep ownership and the room Controller state. |
ERR_FULL is deliberately absent. Some other Creep APIs use ERR_FULL, but it is not a universal Store-capacity result and is not returned by Source harvest().
Why use FIND_SOURCES_ACTIVE?
FIND_SOURCES_ACTIVE gives this beginner loop Sources that currently contain Energy. That reduces one avoidable failure branch while you are learning the action flow.
This is a target-selection policy, not a requirement of harvest(). A dedicated miner can stay assigned to one Source and wait for regeneration; a general-purpose first worker usually benefits from selecting an active Source.
Harden the capacity boundary after the basic loop works
The full-Store rule above is intentionally minimal. A larger unboosted Creep can have several active WORK parts, so one Source harvest batch can be larger than the remaining Store capacity.
The current public engine processor adds the harvested amount and drops overflow when total carried resources exceed Store capacity. For a mobile beginner worker, deliberately overflowing the Store is usually unnecessary. Linqingan therefore uses this conservative project policy for an unboosted Source-harvesting carrier: if the Creep already carries Energy and the remaining capacity is smaller than the next full Source batch, switch to delivery a little early.
function getHarvestCapacityState(creep) {
const activeWork = creep.getActiveBodyparts(WORK);
const usedEnergy =
creep.store.getUsedCapacity(RESOURCE_ENERGY) ?? 0;
const freeEnergy =
creep.store.getFreeCapacity(RESOURCE_ENERGY) ?? 0;
const energyCapacity =
creep.store.getCapacity(RESOURCE_ENERGY) ?? 0;
const nextBatch = activeWork * HARVEST_POWER;
if (energyCapacity <= 0) {
return 'no-energy-capacity';
}
if (nextBatch > energyCapacity) {
return 'body-store-mismatch';
}
if (
freeEnergy === 0
|| (usedEnergy > 0 && freeEnergy < nextBatch)
) {
return 'ready-for-delivery';
}
return 'can-harvest';
}
body-store-mismatch means one unboosted Source batch is larger than the Creep's entire Energy capacity. Change the body or use a deliberate drop-mining design instead of pretending all of that batch can fit.
This formula is not an official Screeps recommendation. It is a local policy for the unboosted beginner body in this lesson. Boosted WORK parts need a different batch calculation.
Debug state, action, and movement separately
| Layer | Question | Evidence to inspect |
|---|---|---|
| State | Should this Creep still be acquiring Energy? | store.getUsedCapacity() and store.getFreeCapacity(). |
| Action | Was the Source harvest accepted? | The exact value returned by harvest(). |
| Movement | If range failed, was movement accepted? | The separate moveTo() result and the later Creep position. |
If you overwrite the harvest result with the movement result, you lose the evidence that tells you which layer failed.
How to verify the loop in your room
Start with read-only observations. Pick your actual Creep rather than copying a placeholder name, then inspect its Store, active WORK parts, position, and the active Sources visible in its room.
When testing the loop, preserve the harvestResult. If it is OK, inspect a later tick when you need to confirm the processed Source/Store change. If it is ERR_NOT_IN_RANGE, preserve the separate movement result and verify that the Creep's later position changed as expected.
Evidence boundary: the API and public engine paths above were reviewed, but this article does not claim a captured live full-Store or near-full overflow trace. Those runtime cases remain Pending.
Next lesson
Once the Creep is ready to deliver, continue with Creep-to-Spawn Energy delivery. If the harvest action keeps returning ERR_NOT_IN_RANGE, use the range debugging guide to separate pathing and movement failures from the harvest call.