SCREEPS API SAFETY · CONSTRUCTION

How to Remove a Construction Site Safely in Screeps

Remove a misplaced Screeps construction site safely with read-only checks, ID validation, one-shot JavaScript, return-code handling, and next-tick verification.

ScreepsConstructionSiteJavaScriptDebugging

VERIFICATION

Verification status

Chinese source
Read in full
Official documentation
Checked
API and constants
Checked
JavaScript syntax
Checked
Offline logic review
Passed
Screeps Console
Pending
Live multi-tick test
Pending
Last verified
July 24, 2026

Quick answer

Use ConstructionSite.remove() to remove an unfinished Construction Site. Before calling it, confirm the site's ID, ownership, room, coordinates, structure type, and current progress. Submit the request only once, store the return code, and verify the original ID and position on a later tick.

The minimum API call is:

const result = site.remove();

That line changes game state. A safer workflow separates the operation into three stages:

  1. Inspect the target without changing anything.
  2. Validate the target and submit one removal request.
  3. Verify the original object and coordinates on the next visible tick.

Construction Site or completed Structure?

A Construction Site becomes a completed Structure when construction finishes. The object type and removal method are different.

Target stateObject typeRelevant method
Still under constructionConstructionSitesite.remove()
Construction completedStructurestructure.destroy()

Do not create a generic function that destroys whatever occupies a coordinate. A target-selection mistake could then affect completed buildings as well as unfinished sites.

Stage 1: Inspect the target without changing the game

State impact: Read-only. This Console snippet does not remove an object or write to Memory.

Replace the example room and coordinates before running it.

const room = Game.rooms.W1N1;

if (!room) {
  console.log('Room W1N1 is not visible.');
} else {
  const sites = room.lookForAt(
    LOOK_CONSTRUCTION_SITES,
    20,
    20
  );

  console.log(JSON.stringify(
    sites.map((site) => ({
      id: site.id,
      my: site.my,
      roomName: site.pos.roomName,
      x: site.pos.x,
      y: site.pos.y,
      structureType: site.structureType,
      progress: site.progress,
      progressTotal: site.progressTotal
    }))
  ));
}

Before continuing, confirm that the result contains the intended site, my is true, the room and coordinates are correct, the structure type matches, and the progress snapshot has been reviewed.

If the lookup returns no site, stop. Do not replace the check with code that removes the first unrelated object found in the room.

Stage 2: Create an explicit one-time request

State impact: The next snippet writes a request to Memory. It does not call remove() by itself.

Memory.removeSiteRequest = {
  enabled: true,
  siteId: 'REPLACE_WITH_SITE_ID',
  roomName: 'W1N1',
  x: 20,
  y: 20,
  expectedType: STRUCTURE_ROAD,
  confirmation: 'REMOVE_CONSTRUCTION_SITE'
};
FieldPurpose
siteIdRetrieves the exact object inspected earlier.
roomNameRejects an object in an unexpected room.
x and yConfirm that the recovered object is still at the expected position.
expectedTypeRejects a different Construction Site type.
confirmationRequires a deliberate and exact confirmation phrase.

Complete one-time removal and verification code

State impact: This code modifies Memory and calls remove() once when every precheck passes. It does not automatically retry a failed operation.

Call both functions from your existing module.exports.loop. Replace the example request values before enabling it.

const REMOVE_SITE_CONFIRMATION = 'REMOVE_CONSTRUCTION_SITE';

function evaluateRemoveSiteRequest(request, site) {
  if (!request || request.enabled !== true) {
    return { ready: false, reason: 'disabled' };
  }

  const validCoordinates =
    Number.isInteger(request.x) &&
    Number.isInteger(request.y) &&
    request.x >= 0 &&
    request.x <= 49 &&
    request.y >= 0 &&
    request.y <= 49;

  if (
    typeof request.siteId !== 'string' ||
    typeof request.roomName !== 'string' ||
    !validCoordinates ||
    typeof request.expectedType !== 'string' ||
    request.confirmation !== REMOVE_SITE_CONFIRMATION
  ) {
    return { ready: false, reason: 'invalid-request' };
  }

  if (!site) {
    return { ready: false, reason: 'site-missing' };
  }

  if (site.my !== true) {
    return { ready: false, reason: 'not-owner' };
  }

  if (site.pos.roomName !== request.roomName) {
    return { ready: false, reason: 'room-mismatch' };
  }

  if (site.pos.x !== request.x || site.pos.y !== request.y) {
    return { ready: false, reason: 'position-mismatch' };
  }

  if (site.structureType !== request.expectedType) {
    return { ready: false, reason: 'type-mismatch' };
  }

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

function submitRemoveSiteRequest() {
  const request = Memory.removeSiteRequest;

  if (!request || request.enabled !== true) {
    return;
  }

  const site = typeof request.siteId === 'string'
    ? Game.getObjectById(request.siteId)
    : null;

  const plan = evaluateRemoveSiteRequest(request, site);

  if (!plan.ready) {
    request.enabled = false;
    request.status = 'precheck-' + plan.reason;
    request.checkedAt = Game.time;
    return;
  }

  request.enabled = false;
  request.status = 'submitted';
  request.submittedAt = Game.time;
  request.snapshot = {
    siteId: site.id,
    roomName: site.pos.roomName,
    x: site.pos.x,
    y: site.pos.y,
    structureType: site.structureType,
    progress: site.progress,
    progressTotal: site.progressTotal
  };

  const result = site.remove();

  request.result = result;
  request.resultAt = Game.time;
  request.status = result === OK
    ? 'accepted'
    : 'failed-review-required';

  console.log(JSON.stringify({
    type: 'remove-site-result',
    siteId: site.id,
    roomName: site.pos.roomName,
    x: site.pos.x,
    y: site.pos.y,
    structureType: site.structureType,
    result
  }));
}

function verifyRemoveSiteRequest() {
  const request = Memory.removeSiteRequest;

  if (
    !request ||
    request.status !== 'accepted' ||
    typeof request.resultAt !== 'number' ||
    Game.time <= request.resultAt
  ) {
    return;
  }

  const originalSite = Game.getObjectById(request.siteId);

  if (originalSite) {
    request.status = 'verification-review-required';
    request.verification = {
      checkedAt: Game.time,
      reason: 'original-site-still-present'
    };
    return;
  }

  const room = Game.rooms[request.roomName];

  if (!room) {
    request.verification = {
      checkedAt: Game.time,
      reason: 'pending-no-room-vision'
    };
    return;
  }

  const sitesAtPosition = room.lookForAt(
    LOOK_CONSTRUCTION_SITES,
    request.x,
    request.y
  );

  request.verification = {
    checkedAt: Game.time,
    reason: sitesAtPosition.length === 0
      ? 'original-site-absent-and-position-clear'
      : 'original-site-absent-but-position-occupied',
    sitesAtPosition: sitesAtPosition.map((site) => ({
      id: site.id,
      my: site.my,
      structureType: site.structureType
    }))
  };

  request.status = sitesAtPosition.length === 0
    ? 'verified-removed'
    : 'verified-original-removed-position-occupied';
}

module.exports.loop = function () {
  submitRemoveSiteRequest();
  verifyRemoveSiteRequest();
};

How the code behaves across ticks

On the submission tick

The handler retrieves the site by ID and validates the request. A failed precheck disables the request and stores a reason such as precheck-site-missing, precheck-not-owner, precheck-position-mismatch, or precheck-type-mismatch.

When every check passes, the code disables the request before calling remove(). This prevents the same enabled flag from submitting another operation on a later tick.

On the next visible tick

The verifier waits until Game.time is greater than the submission tick. It then checks the original ID and the original position.

  • If the original object still exists, the request is marked for review.
  • If the original object is absent but the room is not visible, verification remains pending.
  • If the room is visible and the position is clear, the status becomes verified-removed.
  • If another Construction Site occupies the position, the code records it instead of treating it as the original site.

ConstructionSite.remove() return codes

Return codeMeaningRecommended response
OKThe removal was scheduled successfully.Verify the original ID and coordinates on a later tick.
ERR_NOT_OWNERYou are not the owner of the site, and the site is not in your room.Stop and recheck the ID, ownership, room, and request data.

Do not convert an unexpected result into an automatic retry. A failed request may contain stale or incorrect target information.

Why Game.getObjectById() returning null is not enough

Game.getObjectById() returns an object or null, but it only accesses objects in rooms currently visible to you. A null result can therefore mean that the object no longer exists or that the room is not visible.

The verifier checks Game.rooms[roomName] before declaring the removal verified.

What happens to existing construction progress?

The site exposes progress and progressTotal. The code saves both values before submitting the operation. Review this snapshot before enabling the request, especially when substantial work has already been invested.

This example does not transfer progress to a replacement site and does not claim that invested resources will be recovered in a specific form.

Common mistakes

Calling remove() on null

Game.getObjectById(id) can return null. Retrieve the object first and check it before calling a method.

Deleting only by coordinate

Coordinates are useful for inspection, but the operation should use a confirmed ID and additional target checks.

Using remove() on a completed building

A completed object is a Structure, not a ConstructionSite.

Leaving the request enabled

A Builder may finish the site, another module may remove it, or a new site may later appear at the same position. Disable failed requests and inspect again.

Assuming OK means immediate disappearance

OK reports that the operation was scheduled. Use a later tick as the verification boundary.

Debugging checklist

  • Confirm the target is an unfinished ConstructionSite.
  • Run the read-only coordinate inspection first.
  • Copy the exact site ID.
  • Confirm site.my === true.
  • Confirm the room, coordinates, and expected structure type.
  • Review the progress snapshot.
  • Use the exact confirmation string.
  • Disable the request before submitting remove().
  • Save and inspect the return code.
  • Do not automatically retry failures.
  • Verify only after the submission tick.
  • Confirm room visibility before declaring success.

Safety boundaries

This guide does not implement batch deletion, automatic blueprint cleanup, removal based only on structure type, deletion of completed Structures, construction progress migration, or automatic rebuilding at another coordinate.

Each of those tasks needs separate target-selection, confirmation, and recovery rules.

Frequently asked questions

Can I remove a completed Structure with ConstructionSite.remove()?

No. A completed building is a Structure and requires a separate removal workflow.

Why is the Construction Site still visible after remove() returns OK?

OK means the operation was scheduled. Check the original ID and position on a later tick.

Why does ConstructionSite.remove() return ERR_NOT_OWNER?

The official API returns ERR_NOT_OWNER when the site is not yours and is not in your room. Recheck the ID, site.my, and the room.

Should failed removal requests retry automatically?

No. Disable the request, inspect the current target again, and create a new explicit request after identifying the failure.

Does Game.getObjectById() returning null prove the site was deleted?

Not by itself. Confirm room visibility and inspect the original coordinates before recording the removal as verified.

Official documentation