Skip to main content
PocketBase provides several utility namespaces and functions to help you build robust applications.

HTTP client

The $http namespace provides methods for making HTTP requests.

Basic request

const response = $http.send({
  url: "https://api.example.com/data",
  method: "GET",
  timeout: 30, // seconds
})

console.log("Status:", response.statusCode)
console.log("Body:", toString(response.body))
console.log("JSON:", response.json)

POST request with JSON

const response = $http.send({
  url: "https://api.example.com/users",
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer TOKEN",
  },
  body: JSON.stringify({
    name: "John Doe",
    email: "john@example.com",
  }),
  timeout: 30,
})

if (response.statusCode === 201) {
  console.log("User created:", response.json)
} else {
  console.log("Error:", response.statusCode)
}

POST with FormData

const formData = new FormData()
formData.append("name", "John Doe")
formData.append("email", "john@example.com")

const response = $http.send({
  url: "https://api.example.com/upload",
  method: "POST",
  body: formData,
  timeout: 60,
})

console.log("Response:", response.json)

Handle response

const response = $http.send({
  url: "https://api.example.com/data",
  method: "GET",
})

// Response object properties
console.log("Status code:", response.statusCode)
console.log("Headers:", response.headers)
console.log("Cookies:", response.cookies)
console.log("Body (bytes):", response.body)
console.log("Body (string):", toString(response.body))
console.log("JSON:", response.json) // Auto-parsed if JSON

Security utilities

The $security namespace provides cryptographic and security functions.

Hashing

// MD5
const md5Hash = $security.md5("hello world")
console.log("MD5:", md5Hash)

// SHA256
const sha256Hash = $security.sha256("hello world")
console.log("SHA256:", sha256Hash)

// SHA512
const sha512Hash = $security.sha512("hello world")
console.log("SHA512:", sha512Hash)

// HMAC SHA256
const hmac256 = $security.hs256("hello world", "secret-key")
console.log("HMAC-SHA256:", hmac256)

// HMAC SHA512
const hmac512 = $security.hs512("hello world", "secret-key")
console.log("HMAC-SHA512:", hmac512)

Secure comparison

// Constant-time comparison (prevents timing attacks)
const hash1 = $security.sha256("password123")
const hash2 = $security.sha256("password123")

const isEqual = $security.equal(hash1, hash2)
console.log("Hashes match:", isEqual)

Random strings

// Cryptographically secure random string (default 32 chars)
const token = $security.randomString(32)
console.log("Token:", token)

// Custom alphabet
const customToken = $security.randomStringWithAlphabet(16, "0123456789ABCDEF")
console.log("Hex token:", customToken)

// Regex pattern
const pinCode = $security.randomStringByRegex("[0-9]{6}")
console.log("PIN:", pinCode)

// Pseudo-random (faster, less secure)
const pseudo = $security.pseudorandomString(32)
console.log("Pseudo:", pseudo)

const pseudoCustom = $security.pseudorandomStringWithAlphabet(16, "ABCDEFGH12345678")
console.log("Pseudo custom:", pseudoCustom)

Encryption

// Encrypt data
const key = "my-secret-key-32-chars-long!!!"
const plaintext = "sensitive data"
const encrypted = $security.encrypt(plaintext, key)

console.log("Encrypted:", encrypted)

// Decrypt data
const decrypted = $security.decrypt(encrypted, key)
console.log("Decrypted:", decrypted)

JWT tokens

// Create JWT
const payload = {
  userId: "user123",
  role: "admin",
  exp: Math.floor(Date.now() / 1000) + (60 * 60), // 1 hour
}

const token = $security.createJWT(
  payload,
  "my-secret-key",
  3600 // 1 hour in seconds
)

console.log("JWT:", token)

// Parse JWT (without verification)
const unverified = $security.parseUnverifiedJWT(token)
console.log("Payload:", unverified)

// Parse and verify JWT
try {
  const verified = $security.parseJWT(token, "my-secret-key")
  console.log("Verified payload:", verified)
} catch (err) {
  console.log("Invalid token:", err.message)
}

Cron jobs

Schedule recurring tasks with cron expressions.

Basic cron job

// Runs every hour
cronAdd("cleanup", "0 * * * *", () => {
  console.log("Running cleanup task")
  
  // Delete old logs
  const cutoff = new DateTime()
  cutoff.addDate(0, 0, -30) // 30 days ago
  
  const logs = arrayOf(new Record())
  $app.recordQuery("logs")
    .andWhere($dbx.exp("created < {:cutoff}", {cutoff: cutoff.string()}))
    .all(logs)
  
  for (let log of logs) {
    $app.delete(log)
  }
  
  console.log("Deleted", logs.length, "old logs")
})

Cron expressions

// Every 5 minutes
cronAdd("task1", "*/5 * * * *", () => {
  console.log("Every 5 minutes")
})

// Every day at midnight
cronAdd("task2", "0 0 * * *", () => {
  console.log("Daily task")
})

// Every Monday at 9 AM
cronAdd("task3", "0 9 * * 1", () => {
  console.log("Monday morning task")
})

// Every 1st day of month at 3 AM
cronAdd("task4", "0 3 1 * *", () => {
  console.log("Monthly task")
})

Remove cron job

// Remove a cron job
cronRemove("cleanup")

Date and time

DateTime object

// Current date/time
const now = new DateTime()
console.log("Now:", now.string())

// Parse from string
const date = new DateTime("2024-01-15 10:30:00.000Z")
console.log("Parsed:", date.string())

// With timezone
const date2 = new DateTime("2024-01-15 10:30:00", "America/New_York")
console.log("With timezone:", date2.string())

Date manipulation

const date = new DateTime()

// Add time
date.addDate(1, 2, 3) // Add 1 year, 2 months, 3 days
date.addTime(4, 5, 6, 0) // Add 4 hours, 5 minutes, 6 seconds

console.log("Modified:", date.string())

Timezone

// Create timezone
const tz = new Timezone("America/Los_Angeles")

// Use with DateTime
const date = new DateTime("2024-01-15 10:00:00", "America/Los_Angeles")
console.log("LA time:", date.string())

Type conversion

toString

// Convert various types to string
const str1 = toString(123) // "123"
const str2 = toString(true) // "true"
const str3 = toString([1, 2, 3]) // "[1,2,3]"
const str4 = toString({key: "value"}) // '{"key":"value"}'

// Convert reader to string
const body = toString(e.request.body, 1048576) // Max 1MB

toBytes

// Convert to byte array
const bytes1 = toBytes("hello") // [104, 101, 108, 108, 111]
const bytes2 = toBytes(123) // [49, 50, 51]
const bytes3 = toBytes({key: "value"}) // JSON bytes

// Convert reader to bytes
const bodyBytes = toBytes(e.request.body, 1048576) // Max 1MB

unmarshal

// Merge data into object
const record = new Record()
unmarshal({title: "Hello", content: "World"}, record)

console.log(record.getString("title")) // "Hello"
console.log(record.getString("content")) // "World"

Arrays

arrayOf

// Create typed array for database results
const records = arrayOf(new Record())
$app.recordQuery("posts").limit(10).all(records)

console.log("Found", records.length, "posts")

// For collections
const collections = arrayOf(new Collection())
$app.findAllCollections(collections)

Sleep

// Pause execution
console.log("Starting...")
sleep(2000) // Sleep for 2 seconds (2000ms)
console.log("Done!")

Context

Create Go context objects for API calls:
// Create blank context
const ctx = new Context()

// Create with value
const ctx2 = new Context(null, "requestId", "12345")
console.log(ctx2.value("requestId")) // "12345"

// Chain contexts
const ctx3 = new Context(ctx2, "userId", "user123")
console.log(ctx3.value("requestId")) // "12345"
console.log(ctx3.value("userId")) // "user123"

Validation

ValidationError

// Create validation error
const error = new ValidationError("validation_required", "This field is required")

// Throw in hook
onRecordValidate((e) => {
  if (e.collection.name === "posts") {
    const title = e.record.getString("title")
    
    if (!title || title.length < 3) {
      throw new ValidationError(
        "validation_min_length",
        "Title must be at least 3 characters"
      )
    }
  }
  return e.next()
}, "posts")

API errors

Create proper HTTP error responses:
// Bad Request (400)
throw new BadRequestError("Invalid input")

// Unauthorized (401)
throw new UnauthorizedError("Authentication required")

// Forbidden (403)
throw new ForbiddenError("Access denied")

// Not Found (404)
throw new NotFoundError("Resource not found")

// Too Many Requests (429)
throw new TooManyRequestsError("Rate limit exceeded")

// Internal Server Error (500)
throw new InternalServerError("Something went wrong")

// Custom API error
throw new ApiError(418, "I'm a teapot", null)

Environment variables

// Get environment variable
const apiKey = $os.getenv("API_KEY")
const environment = $os.getenv("ENVIRONMENT") || "production"

console.log("API Key:", apiKey)
console.log("Environment:", environment)

Examples

Webhook integration

onRecordAfterCreateSuccess((e) => {
  if (e.collection.name === "orders") {
    const record = e.record
    
    // Send webhook notification
    try {
      const response = $http.send({
        url: "https://webhook.example.com/orders",
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Webhook-Secret": $os.getenv("WEBHOOK_SECRET"),
        },
        body: JSON.stringify({
          event: "order.created",
          order_id: record.id,
          total: record.getFloat("total"),
          customer: record.getString("customer"),
        }),
        timeout: 10,
      })
      
      console.log("Webhook sent:", response.statusCode)
    } catch (err) {
      console.log("Webhook failed:", err.message)
    }
  }
  return e.next()
}, "orders")

Rate limiting

const rateLimits = {}

routerAdd("POST", "/api/expensive-operation", (e) => {
  const clientIP = e.realIP()
  const now = Date.now()
  const limit = 10 // Max 10 requests
  const window = 60000 // Per 60 seconds
  
  // Initialize or clean up old entries
  if (!rateLimits[clientIP]) {
    rateLimits[clientIP] = []
  }
  
  // Remove expired timestamps
  rateLimits[clientIP] = rateLimits[clientIP].filter(
    timestamp => now - timestamp < window
  )
  
  // Check limit
  if (rateLimits[clientIP].length >= limit) {
    throw new TooManyRequestsError("Rate limit exceeded. Try again later.")
  }
  
  // Add current request
  rateLimits[clientIP].push(now)
  
  // Process request
  return e.json(200, {success: true})
}, $apis.requireAuth())

API key authentication

const apiKeyMiddleware = new Middleware((e) => {
  const apiKey = e.request.header.get("X-API-Key")
  
  if (!apiKey) {
    throw new UnauthorizedError("API key required")
  }
  
  // Verify API key
  const validKey = $os.getenv("API_KEY")
  const isValid = $security.equal(apiKey, validKey)
  
  if (!isValid) {
    throw new UnauthorizedError("Invalid API key")
  }
  
  return e.next()
}, 0, "api_key_auth")

routerAdd("GET", "/api/protected", (e) => {
  return e.json(200, {message: "Access granted"})
}, apiKeyMiddleware)