Quick answer
Use getUsedCapacity(resource) for the amount already stored, getFreeCapacity(resource) for the amount that can still be accepted, and getCapacity(resource) for the maximum capacity that applies to that resource. The difficult part is not the method names. It is knowing whether the Store has one shared capacity, several resource-specific capacities, or no writable capacity at all.
function inspectStoreResource(
object,
resourceType
) {
if (!object?.store) {
return {
status: 'store-missing'
};
}
const used = object.store.getUsedCapacity(
resourceType
);
const free = object.store.getFreeCapacity(
resourceType
);
const capacity = object.store.getCapacity(
resourceType
);
return {
status:
used === null
&& free === null
&& capacity === null
? 'resource-unsupported'
: 'store-observed',
resourceType,
used,
free,
capacity
};
}
Never collapse null into zero with value || 0. Zero usually means the query is valid but empty or full. Null means the requested capacity does not apply, the resource is unsupported by that limited Store, or the object has no writable capacity.
Separate general, limited, and read-only Stores
The official API describes two capacity models. General-purpose Stores use one shared capacity for many resources. Creep, Container, Storage, Terminal, and Factory are common examples. Limited Stores accept only the resources required by their object, such as Energy in a Spawn or Tower, Energy and Power in a Power Spawn, or Energy and one mineral channel in a Lab.
A third operational category is useful in code: read-only resource Stores such as Tombstones and Ruins. They expose stored amounts for withdrawal but do not expose writable capacity.
| Category | Examples | Capacity behavior |
|---|---|---|
| General | Creep, Container, Storage, Terminal, Factory | Resources share one total capacity |
| Limited | Spawn, Extension, Tower, Lab, Power Spawn, Nuker | Capacity depends on a valid resource argument |
| Read-only | Tombstone, Ruin | Used amount is readable; capacity and free space are null |
Ask the three capacity questions correctly
function readCapacityTriplet(
store,
resourceType
) {
return {
used: store.getUsedCapacity(resourceType),
free: store.getFreeCapacity(resourceType),
capacity: store.getCapacity(resourceType)
};
}
For a general Store, omitting the resource from getUsedCapacity() returns total used capacity. Omitting it from getCapacity() or getFreeCapacity() returns the shared total and shared remaining capacity. For a limited Store, an omitted resource often produces null because there is no single unrestricted capacity.
Do not assume used + free === capacity until all three values are finite numbers.
function capacityIsConsistent(snapshot) {
return Number.isFinite(snapshot.used)
&& Number.isFinite(snapshot.free)
&& Number.isFinite(snapshot.capacity)
&& snapshot.used + snapshot.free
=== snapshot.capacity;
}
Treat zero and null as different states
function classifyFreeCapacity(value) {
if (value === null) {
return 'capacity-not-applicable';
}
if (value === 0) {
return 'store-full';
}
if (Number.isFinite(value) && value > 0) {
return 'space-available';
}
return 'unexpected-capacity-value';
}
An empty valid resource slot returns zero used capacity. A full valid Store returns zero free capacity. A Spawn queried for Power returns null because Power is not valid for that Store. A Tombstone queried for free Energy capacity also returns null because it cannot receive resources.
Understand shared capacity in general Stores
Suppose a Storage has total capacity 2,000 and currently contains 700 Energy and 200 Hydrogen. Its total used capacity is 900 and its shared free capacity is 1,100. Both of these calls return 1,100:
const freeForEnergy = storage.store
.getFreeCapacity(RESOURCE_ENERGY);
const freeForHydrogen = storage.store
.getFreeCapacity(RESOURCE_HYDROGEN);
The result does not mean Energy and Hydrogen each have a separate 1,100 slot. They compete for the same remaining space. Resource-specific getUsedCapacity() reports the amount of that resource; resource-specific getFreeCapacity() on a general Store reports the remaining shared capacity.
Pass a resource to limited Stores
function inspectEnergyOnlyStructure(
structure
) {
const used = structure.store.getUsedCapacity(
RESOURCE_ENERGY
);
const free = structure.store.getFreeCapacity(
RESOURCE_ENERGY
);
const capacity = structure.store.getCapacity(
RESOURCE_ENERGY
);
if (
used === null
|| free === null
|| capacity === null
) {
return {
status: 'energy-capacity-unavailable'
};
}
return {
status: free > 0
? 'needs-energy'
: 'full',
used,
free,
capacity
};
}
Spawn, Extension, and Tower code should explicitly pass RESOURCE_ENERGY. A request for an unsupported resource should remain null rather than being silently treated as an empty slot. That distinction catches bad resource routing before transfer() returns an error.
Handle Lab and specialized capacities
A Lab has an Energy capacity and a separate mineral or compound capacity. Power Spawn and Nuker also have multiple allowed resources with separate limits. A total capacity call cannot express those independent channels reliably.
function inspectLabStore(
lab,
mineralType
) {
return {
energy: inspectStoreResource(
lab,
RESOURCE_ENERGY
),
mineral: inspectStoreResource(
lab,
mineralType
)
};
}
Use the exact intended mineral type. A Lab that already contains one compound may not be a valid destination for a different compound even when a generic mineral-capacity calculation appears nonzero. Capacity is one precondition; object-specific action rules and return codes still apply.
Recognize Tombstone and Ruin Stores
function canReceiveResource(
object,
resourceType
) {
if (!object?.store) {
return false;
}
const free = object.store.getFreeCapacity(
resourceType
);
return Number.isFinite(free)
&& free > 0;
}
A Tombstone or Ruin can report getUsedCapacity(resource) and total used capacity, but its capacity and free-capacity queries are null. The existence of object.store therefore does not prove that the object is a legal transfer destination.
Do not infer validity from Object.keys
The public engine Store proxy returns zero when a known resource property is absent, but enumeration normally exposes only nonzero resources. This means Object.keys(store) is useful for listing currently present resources, not for testing every supported resource.
function listStoredResources(store) {
return Object.keys(store)
.filter(resourceType =>
store.getUsedCapacity(resourceType) > 0
)
.map(resourceType => ({
resourceType,
amount: store.getUsedCapacity(resourceType)
}));
}
Use a known resource constant and a Store method when you need to determine its amount or capacity behavior.
Calculate a safe withdraw amount
function calculateWithdrawAmount(
creep,
source,
resourceType,
requestedAmount = Infinity
) {
if (!creep?.store || !source?.store) {
return {
status: 'store-missing',
amount: 0
};
}
const available = source.store
.getUsedCapacity(resourceType);
const free = creep.store
.getFreeCapacity(resourceType);
if (available === null || free === null) {
return {
status: 'resource-unsupported',
amount: 0
};
}
const amount = Math.min(
available,
free,
Number.isFinite(requestedAmount)
? Math.max(0, requestedAmount)
: Infinity
);
return {
status: amount > 0
? 'amount-ready'
: 'nothing-to-withdraw',
amount,
available,
free
};
}
The amount is bounded by source stock, Creep free capacity, and any caller limit. It is still a current-tick calculation, not proof that the later action will complete.
Calculate a safe transfer amount
function calculateTransferAmount(
creep,
target,
resourceType,
requestedAmount = Infinity
) {
if (!creep?.store || !target?.store) {
return {
status: 'store-missing',
amount: 0
};
}
const carried = creep.store
.getUsedCapacity(resourceType);
const free = target.store
.getFreeCapacity(resourceType);
if (carried === null || free === null) {
return {
status: 'resource-unsupported',
amount: 0
};
}
const amount = Math.min(
carried,
free,
Number.isFinite(requestedAmount)
? Math.max(0, requestedAmount)
: Infinity
);
return {
status: amount > 0
? 'amount-ready'
: 'nothing-to-transfer',
amount,
carried,
free
};
}
Also validate range, ownership, active structure state, object-specific resource rules, and the actual transfer() return code.
Verify Store changes on a later tick
function createStoreActionEvidence(
creep,
target,
resourceType,
amount,
result
) {
return {
creepName: creep.name,
targetId: target.id ?? null,
resourceType,
amount,
submittedAt: Game.time,
result,
before: {
creepUsed: creep.store.getUsedCapacity(
resourceType
),
targetUsed: target.store.getUsedCapacity(
resourceType
)
}
};
}
An OK action result means the command was accepted. It does not mutate the script-visible Store immediately. Resolve the same identities on a later tick and compare bounded numeric deltas before reporting the transfer or withdrawal as observed.
Debugging checklist
- Confirm that the object exists and has a Store.
- Classify it as general, limited, or read-only.
- Pass a resource argument to limited Stores.
- Record used, free, and capacity separately.
- Distinguish zero from null explicitly.
- Remember that general resources share one remaining capacity.
- Inspect Lab, Power Spawn, and Nuker resources independently.
- Use Tombstone and Ruin only as withdrawal sources.
- Bound action amounts by source stock and destination space.
- Preserve the action result and verify Store deltas later.
Evidence and production boundary
This revision checks the official Store API, the public engine Store implementation, and the engine Store test suite. Repository tests syntax-check every example and cover general shared capacity, limited resource compatibility, null versus zero, Lab resource channels, read-only Stores, amount calculation, and later-delta classification.
Screeps Console execution, official-shard action settlement, concurrent hauler contention, Power effects, structure activation changes, and live CPU cost remain pending. The guide does not claim that a capacity snapshot guarantees an action result.
Official and source references: Store API, Creeps and resources, public Store implementation, and public Store tests.