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:
| Type | File | Purpose |
|---|---|---|
| Pre-hook | {slug}.hooks.js | Modify request before sending |
| Post-hook | {slug}.hooks.js | Process response after receiving |
| Test | {slug}.test.js | Validate 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
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
| Property | Type | Description |
|---|---|---|
ctx.env | EnvironmentContext | Environment variables (read/write) |
ctx.store | SessionStore | Session storage (in-memory, clears on exit) |
ctx.secrets | Record<string, string> | Read-only secrets from .secrets.json |
ctx.log | Logger | Logging utilities |
ctx.request | MutableRequest | Modifiable request object |
Environment Methods
// Read environment variablesctx.env.get('API_TOKEN') // Get a variablectx.env.has('API_TOKEN') // Check if existsctx.env.all() // Get all as object
// Write to environment (persists to disk)await ctx.env.set('TOKEN', value) // Set and persistValues set with ctx.env.set() are saved to the active environment file and available to subsequent requests via {{TOKEN}}.
Request Methods
// Headersctx.request.setHeader('X-Custom', 'value')ctx.request.removeHeader('X-Custom')
// Query parametersctx.request.setQueryParam('page', '1')ctx.request.removeQueryParam('page')
// Bodyctx.request.setBody(JSON.stringify({ data: 'value' }))
// Read-only propertiesctx.request.method // 'GET', 'POST', etc.ctx.request.url // Full URLctx.request.headers // All headersctx.request.body // Current bodyPost-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
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:
| Property | Type | Description |
|---|---|---|
ctx.response | ImmutableResponse | Read-only response object |
ctx.timing | Timing | Request 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-299ctx.response.headers // Response headersctx.response.body // Raw response body
// Parse responsectx.response.json() // Parse as JSONctx.response.text() // Get as textTiming Properties
ctx.timing.startTime // Request start timestampctx.timing.endTime // Request end timestampctx.timing.duration // Duration in millisecondsCollection Hooks
Collection hooks run for all requests in a collection. They’re useful for shared logic like authentication.
File Location
collections/{collection-id}/hooks.jsExecution Order
- Collection pre-hook
- Request pre-hook
- HTTP request sent
- Request post-hook
- Collection post-hook
Example
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
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
- Press
ctrl+shift+returnon a request with tests - Select Send with Tests
- 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 variablesctx.store- Session storagectx.secrets- Secretsctx.log- Loggerctx.request- Request (read-only in tests)ctx.response- Responsectx.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 valuectx.store.has('key') // Check if key existsctx.store.delete('key') // Remove a keyctx.store.clear() // Clear all valuesExample: OAuth Flow (Persistent)
Use ctx.env.set() to persist tokens to the environment file:
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) }}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 testsExamples
Add Timestamp to All Requests
export const pre = async (ctx) => { ctx.request.setHeader('X-Request-Time', new Date().toISOString())}Retry on 401 (Refresh Token)
export const post = async (ctx) => { if (ctx.response.status === 401) { ctx.log.warn('Token expired, refresh needed') ctx.store.delete('access_token') }}Validate Pagination
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) })}Related
- HTTP Mode - Making requests
- Collections - Organizing requests
- Environments - Variable management