Chaining Requests with Variables

9 min read

A login is rarely interesting on its own. What you actually want to test is the flow: log in, take the token, hit a protected endpoint, take an id from the response, fetch that resource, do something to it. Chaining is the technique of passing values from one request's response into the next request's URL, headers, or body via Postman variables. This lesson covers the chaining pattern, an end-to-end CRUD chain you can run in the Collection Runner, and the most common debugging techniques when a chain silently breaks.

The pattern, in three steps

Every chain follows the same shape:

  1. Request A — extract. In the Tests tab, parse the response, pull a value, store it as a collection or environment variable.
  2. Request B — consume. Reference the stored variable somewhere in the request: URL, header, or body.
  3. Repeat. Request B can extract its own value into another variable for Request C, and so on.

A canonical three-step chain — login, list users, fetch one user's orders:

Three requests, two set calls each (token + id, then another id), one {{var}} reference in the next request. That's the whole pattern — once you've seen it once, every chain looks the same.

A concrete login + extract example

In the Tests tab of your POST Login request:

const response = pm.response.json();
 
pm.test("Login successful", () => {
    pm.expect(pm.response.code).to.equal(200);
    pm.expect(response).to.have.property("token");
    pm.expect(response.token).to.be.a("string").and.to.have.lengthOf.above(20);
});
 
// Stash for later requests
pm.collectionVariables.set("authToken", response.token);
pm.collectionVariables.set("userId", response.user.id);
 
console.log("Stored authToken (length):", response.token.length);
console.log("Stored userId:", response.user.id);

Notice two things:

  • The assertions come first. If the response shape isn't what we expected, the test fails loudly. Don't extract from a broken response and pretend everything's fine.
  • The console.log lines are debugging aids you can leave in place. Open the Console (Cmd/Ctrl+Alt+C) and you'll see the values being captured every time you send.

Consuming the variables

In any subsequent request:

  • URL: {{baseUrl}}/users/{{userId}}/orders
  • Header (Authorization tab → Bearer Token): Token = {{authToken}} — or set this once at the collection level and every request inherits it.
  • Body:
    { "ownerId": "{{userId}}", "status": "pending" }

Hover over a {{var}} reference in the URL bar and Postman shows its current resolved value in a tooltip — instant sanity check before sending.

A complete CRUD chain

Putting it together — a five-request collection that creates, reads, updates, deletes, and verifies a resource. Each request's Tests tab does its bit of the chain.

1. POST Create Post ({{baseUrl}}/posts)

const r = pm.response.json();
pm.test("Status is 201", () => pm.response.to.have.status(201));
pm.test("Body has id", () => pm.expect(r).to.have.property("id"));
pm.collectionVariables.set("createdPostId", r.id);

2. GET Verify Created ({{baseUrl}}/posts/{{createdPostId}})

pm.test("Status is 200", () => pm.response.to.have.status(200));
pm.test("Returned post matches the id we saved", () => {
    pm.expect(pm.response.json().id).to.equal(pm.collectionVariables.get("createdPostId"));
});

3. PATCH Update Title ({{baseUrl}}/posts/{{createdPostId}})

pm.test("Status is 200", () => pm.response.to.have.status(200));
pm.test("Title updated", () => {
    pm.expect(pm.response.json().title).to.equal("Updated by chain");
});

4. DELETE Post ({{baseUrl}}/posts/{{createdPostId}})

pm.test("DELETE returned 200", () => pm.response.to.have.status(200));

5. GET Verify Deleted ({{baseUrl}}/posts/{{createdPostId}})

pm.test("Should be 404 (or empty {} on JSONPlaceholder)", () => {
    const body = pm.response.json();
    const isGone = pm.response.code === 404 || Object.keys(body).length === 0;
    pm.expect(isGone, "Resource should be gone after DELETE").to.be.true;
});

Run the five in order via the Collection Runner (Chapter 5) and you've tested the entire lifecycle of one resource — with one shared variable carrying the id all the way through.

Where to store the chained variables

Two scopes do most of the work:

  • Collection variables — perfect for ids that are scoped to a single test run within a collection. They survive across requests but don't leak to other collections.
  • Environment variables — for values that should follow the environment (auth tokens, base URLs, test user emails). Switching from staging to prod swaps them in.

A reasonable convention:

  • Auth token, base URL, test credentials → environment.
  • Per-run ids and intermediate values (createdPostId, lastOrderId) → collection.

You can mix freely; the rules from Chapter 2 still apply (environment beats collection if both exist).

Debugging a chain that silently breaks

When {{userId}} resolves to nothing and the next request 404s, the answer is almost always one of three things. Walk through them in order:

  • Did the previous request actually succeed? A failing login still runs the Tests script — and an extraction like response.token on an error body will produce undefined, which gets stored. Always assert before you extract. If the assertion fails, the chain stops cleanly instead of cascading.
  • Did you target the right path in the response? response.token vs response.data.token is the most common bug. Open the Postman Console — the response body is logged there. Walk the path in your head against what's actually shown.
  • Are you using the right scope? Setting a local variable with pm.variables.set rather than pm.collectionVariables.set means the value disappears at the end of the request. Use pm.environment.set or pm.collectionVariables.set for anything that needs to outlive the current request.

A quick instrumentation pattern:

const r = pm.response.json();
console.log("Full response:", r);
console.log("Looking for r.token at:", r?.token);
 
if (r?.token) {
    pm.collectionVariables.set("authToken", r.token);
    console.log("Stored token, length", r.token.length);
} else {
    console.error("No token in response — chain will break!");
}

The console.logs expose exactly what's happening. Pair this with the eye icon quick-view (top right of the Postman window) which shows the current value of every variable in scope — a one-second check after each request.

Chaining inside a single request

You can also call other requests during a request via pm.sendRequest — useful in pre-request scripts when one request needs another's output but you don't want to make the user run them in order. The auto-refresh-token snippet from the previous lesson is the canonical example.

For most workflows, the explicit ordered-collection approach (one request per step, run via Collection Runner) is easier to read and debug. Reach for pm.sendRequest only when ordering can't be guaranteed.

⚠️ Common mistakes

  • Extracting from a failed response. A failed login that returns {"error": "invalid creds"} doesn't have a .token field — your pm.collectionVariables.set("authToken", response.token) stores undefined. Always assert first.
  • Wrong scope: using pm.variables.set for cross-request data. pm.variables.set creates a local variable that doesn't persist between requests. Use pm.collectionVariables.set or pm.environment.set to share state.
  • Order assumptions in the Collection Runner. The Runner executes requests in the order they appear in the folder. If your chain breaks because requests ran out of order, drag them into the right top-to-bottom sequence. Folders run in their listed order; nested folders run their children first.

🎯 Practice task

Build a real chain. 30-40 minutes.

  1. In JSONPlaceholder API Tests, ensure these requests exist (create them if missing) inside a folder called CRUD Chain:
    • 1. POST Create Post{{baseUrl}}/posts
    • 2. GET Verify Created{{baseUrl}}/posts/{{createdPostId}}
    • 3. PATCH Update Title{{baseUrl}}/posts/{{createdPostId}}
    • 4. DELETE Post{{baseUrl}}/posts/{{createdPostId}}
    • 5. GET Verify Deleted{{baseUrl}}/posts/{{createdPostId}}
  2. Reorder them so the numbers are in sequence inside the folder.
  3. Add the Tests-tab snippets above (one per request). For request 3, set the body to {"title": "Updated by chain"}.
  4. Run the requests one at a time, in order. After each, open the eye-icon quick-view and verify createdPostId is set and being reused. Open the Console to see the console.log output.
  5. Run the chain in one go. Right-click the CRUD Chain folder → Run folder. Postman opens the Collection Runner with the five requests pre-selected. Click Run. Watch each request fire in order with green ticks. (The Runner is covered in depth in Chapter 5.)
  6. Break the chain on purpose. Change request 1's URL to /postsxxx (a path that doesn't exist). Re-run the folder. Notice that request 2 fails or returns wrong data because createdPostId was never properly set. This is exactly the cascade the "assert before extract" rule prevents.
  7. Stretch: add an authentication leg to the chain. Insert a 0. POST Login (use reqres.in's /login endpoint with {"email":"eve.holt@reqres.in","password":"cityslicka"}) that extracts a token and stores it. Make every other request reference {{token}} in an Authorization header. Confirm the chain still runs end-to-end.

You can now build chains that move state across many requests. The next lesson takes the same idea further: instead of one chain, run the same request many times against different inputs from a CSV or JSON file.

// tip to track lessons you complete and pick up where you left off across devices.