Skip to content

Scripts

Add pre/post hooks and tests to your HTTP requests.

Scripts let you extend requests with custom logic. Use pre-request hooks to modify requests before sending, post-request hooks to process responses, and tests to validate responses.


Overview

t-req supports three types of scripts:

TypeFilePurpose
Pre-hook{slug}.hooks.jsModify request before sending
Post-hook{slug}.hooks.jsProcess response after receiving
Test{slug}.test.jsValidate response with assertions

Scripts are JavaScript files stored alongside your saved requests.


Scripts Tab

In HTTP mode, use the Scripts tab to manage scripts for the current request.

The tab shows:

  • Request hooks - Pre/post hooks for this specific request
  • Collection hooks - Hooks that run for all requests in the collection
  • Tests - Test file for this request

Press Enter on any script to create or edit it.


Pre-Request Hooks

Pre-hooks run before the request is sent. Use them to:

  • Add authentication headers
  • Modify request body
  • Set dynamic values

Creating a Pre-Hook

{slug}.hooks.js
export const pre = async (ctx) => {
// Add authorization header
ctx.request.setHeader('Authorization', `Bearer ${ctx.env.API_TOKEN}`)
// Add timestamp to body
const body = JSON.parse(ctx.request.body || '{}')
body.timestamp = Date.now()
ctx.request.setBody(JSON.stringify(body))
}

Pre-Hook Context

PropertyTypeDescription
ctx.envEnvironmentContextEnvironment variables (read/write)
ctx.storeSessionStoreSession storage (in-memory, clears on exit)
ctx.secretsRecord<string, string>Read-only secrets from .secrets.json
ctx.logLoggerLogging utilities
ctx.requestMutableRequestModifiable request object

Environment Methods

// Read environment variables
ctx.env.get('API_TOKEN') // Get a variable
ctx.env.has('API_TOKEN') // Check if exists
ctx.env.all() // Get all as object
// Write to environment (persists to disk)
await ctx.env.set('TOKEN', value) // Set and persist

Values set with ctx.env.set() are saved to the active environment file and available to subsequent requests via {{TOKEN}}.

Request Methods

// Headers
ctx.request.setHeader('X-Custom', 'value')
ctx.request.removeHeader('X-Custom')
// Query parameters
ctx.request.setQueryParam('page', '1')
ctx.request.removeQueryParam('page')
// Body
ctx.request.setBody(JSON.stringify({ data: 'value' }))
// Read-only properties
ctx.request.method // 'GET', 'POST', etc.
ctx.request.url // Full URL
ctx.request.headers // All headers
ctx.request.body // Current body

Post-Request Hooks

Post-hooks run after receiving a response. Use them to:

  • Store tokens for subsequent requests
  • Log response data
  • Transform or validate responses

Creating a Post-Hook

{slug}.hooks.js
export const post = async (ctx) => {
// Store token from response
if (ctx.response.ok) {
const data = ctx.response.json()
ctx.store.set('access_token', data.access_token)
ctx.log.info('Token stored successfully')
} else {
ctx.log.error('Request failed:', ctx.response.status)
}
}

Post-Hook Context

Includes everything from pre-hooks, plus:

PropertyTypeDescription
ctx.responseImmutableResponseRead-only response object
ctx.timingTimingRequest timing information

Response Properties

ctx.response.status // HTTP status code (200, 404, etc.)
ctx.response.statusText // Status text ('OK', 'Not Found', etc.)
ctx.response.ok // true if status is 200-299
ctx.response.headers // Response headers
ctx.response.body // Raw response body
// Parse response
ctx.response.json() // Parse as JSON
ctx.response.text() // Get as text

Timing Properties

ctx.timing.startTime // Request start timestamp
ctx.timing.endTime // Request end timestamp
ctx.timing.duration // Duration in milliseconds

Collection Hooks

Collection hooks run for all requests in a collection. They’re useful for shared logic like authentication.

File Location

collections/{collection-id}/hooks.js

Execution Order

  1. Collection pre-hook
  2. Request pre-hook
  3. HTTP request sent
  4. Request post-hook
  5. Collection post-hook

Example

collections/my-api/hooks.js
export const pre = async (ctx) => {
// Add API key to all requests in this collection
ctx.request.setHeader('X-API-Key', ctx.secrets.API_KEY)
}
export const post = async (ctx) => {
// Log all responses
ctx.log.info(`${ctx.request.method} ${ctx.request.url} -> ${ctx.response.status}`)
}

Tests

Tests validate responses using assertions. They run with the Bun test runner.

Creating a Test

{slug}.test.js
import { test, expect } from 'bun:test'
export default (ctx) => {
test('returns 200 status', () => {
expect(ctx.response.status).toBe(200)
})
test('returns JSON content type', () => {
expect(ctx.response.headers['content-type']).toContain('application/json')
})
test('response has expected shape', () => {
const body = ctx.response.json()
expect(body.id).toBeDefined()
expect(body.name).toBeTypeOf('string')
})
test('response time is acceptable', () => {
expect(ctx.timing.duration).toBeLessThan(1000)
})
}

Running Tests

  1. Press ctrl+shift+return on a request with tests
  2. Select Send with Tests
  3. View results in the response panel

Or use the command palette: “Send with Tests”

Test Context

Tests receive the same context as post-hooks:

  • ctx.env - Environment variables
  • ctx.store - Session storage
  • ctx.secrets - Secrets
  • ctx.log - Logger
  • ctx.request - Request (read-only in tests)
  • ctx.response - Response
  • ctx.timing - Timing data

Session Store

The session store is an in-memory key-value store that persists data across requests within a session. Data is lost when the TUI closes.

For values that need to persist across sessions (like auth tokens), use ctx.env.set() instead.

Methods

ctx.store.set('key', 'value') // Store a value (in-memory only)
ctx.store.get('key') // Retrieve a value
ctx.store.has('key') // Check if key exists
ctx.store.delete('key') // Remove a key
ctx.store.clear() // Clear all values

Example: OAuth Flow (Persistent)

Use ctx.env.set() to persist tokens to the environment file:

login.hooks.js
export const post = async (ctx) => {
if (ctx.response.ok) {
const { access_token, refresh_token } = ctx.response.json()
// Persist to environment file - survives TUI restart
await ctx.env.set('ACCESS_TOKEN', access_token)
await ctx.env.set('REFRESH_TOKEN', refresh_token)
}
}
get-profile.hooks.js
export const pre = async (ctx) => {
const token = ctx.env.get('ACCESS_TOKEN')
if (token) {
ctx.request.setHeader('Authorization', `Bearer ${token}`)
}
}

Logger

Use the logger for debugging and information output.

ctx.log.info('Information message')
ctx.log.warn('Warning message')
ctx.log.error('Error message')
ctx.log.debug('Debug message')

Log output appears in the response panel when hooks or tests run.


File Structure

collections/
└── my-api/
├── collection.json
├── hooks.js # Collection hooks (all requests)
└── get-user/
├── request.json
├── get-user.hooks.js # Request hooks
└── get-user.test.js # Request tests

Examples

Add Timestamp to All Requests

collection hooks.js
export const pre = async (ctx) => {
ctx.request.setHeader('X-Request-Time', new Date().toISOString())
}

Retry on 401 (Refresh Token)

request hooks.js
export const post = async (ctx) => {
if (ctx.response.status === 401) {
ctx.log.warn('Token expired, refresh needed')
ctx.store.delete('access_token')
}
}

Validate Pagination

list-users.test.js
import { test, expect } from 'bun:test'
export default (ctx) => {
test('returns paginated response', () => {
const body = ctx.response.json()
expect(body.data).toBeArray()
expect(body.meta.page).toBeNumber()
expect(body.meta.total).toBeNumber()
})
test('respects limit parameter', () => {
const body = ctx.response.json()
expect(body.data.length).toBeLessThanOrEqual(10)
})
}