# pgsql-http: HTTP requests inside PostgreSQL

> pgsql-http is a C-based PostgreSQL extension that lets SQL call HTTP services and inspect structured responses. It fits database triggers and ETL workflows, but its string-based content model and limited operational documentation require care.

**pramsey/pgsql-http** — HTTP client for PostgreSQL, retrieve a web page from inside the database.

- Repository: https://github.com/pramsey/pgsql-http
- Stars: 1,604 · Forks: 130
- Language: C
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/pramsey-pgsql-http

## Why pgsql-http exists inside PostgreSQL

pgsql-http addresses a specific gap between application services and database-side automation. Its stated motivation is to write a trigger that can call a web service, either to retrieve a result or to ask that service to refresh itself from the database state. That makes the extension relevant to PostgreSQL administrators, developers building ETL processes, and teams whose transaction logic depends on an HTTP endpoint but does not run through a separate application layer.

The project does not position itself as a general external job runner. Its interface is SQL. A query calls `http_get()`, `http_post()`, or another wrapper, and PostgreSQL returns an `http_response` composite containing the status, content type, headers and response body. This keeps the network operation close to SQL statements and triggers, but it also means database permissions, transaction behavior and network availability become part of the integration's design.

The supported request methods are GET, POST, PUT, PATCH, DELETE and HEAD. Request content can be text, a JSON payload built with `jsonb_build_object()`, or binary data converted through the documented `bytea_to_text` function. This covers common notification, resource creation and service lookup patterns without requiring an HTTP client to be embedded in every PostgreSQL query.

## The request, response and header model in pgsql-http

Every call is represented by an `http_request` composite. Its fields are `method`, `uri`, `headers`, `content_type` and `content`. The method uses the `http_method` type, the URI and content fields use `character varying`, and headers are stored as an `http_header[]` array. The response uses a separate `http_response` composite with `status`, `content_type`, `headers` and `content`.

The convenience functions call a master function named `http(http_request)`. This distinction matters when a wrapper is too restrictive. The README demonstrates constructing a request composite directly when a call needs an authorization header, which shows that method, URL, headers, content type and content can be assembled before being passed to the central function.

Headers are tuples rather than ordinary strings. `http_header(field, value)` creates one tuple, while `http_headers(field, value, ...)` creates the array. PostgreSQL's `unnest()` function expands that array so a query can select a particular field or display all response headers. Case comparison therefore needs to account for the exact header text. The README uses `field = 'Location'` in one HEAD example but `field ilike 'Content-Type'` in its binary example, illustrating that callers should not assume one universal matching policy.

Based on the repository layout, the core implementation is in `http.c`. The Makefile obtains compiler and linker settings from `curl-config`, so the extension is built as a PostgreSQL shared library linked against libcurl. SQL entry points are defined separately in `http--1.7.sql` and version upgrade files.

## Getting pgsql-http built and making a first GET request

The README does not give installation commands, a package name, supported PostgreSQL versions, or an extension-creation statement. It also does not provide a documented `CREATE EXTENSION` example. Based on the repository files, the build depends on PostgreSQL's PGXS environment through `pg_config --pgxs` and on libcurl through `curl-config`. The repository's `Makefile` supplies the bridge between those tools, but its presence is not a complete deployment guide.

Consequently, there is no verified installation command to reproduce here. An operator must use the project's packaged distribution, if one exists in the target PostgreSQL environment, or follow a source-build procedure compatible with the local PGXS and libcurl installation. The exact extension-loading command is absent from the README and should not be inferred.

The first documented use is a simple GET request. The following query calls `http_get()` and selects the response body as `content`:

```sql
SELECT content
  FROM http_get('http://httpbun.com/ip');
```

The README shows the returned value as a JSON document containing an `origin` field. This example depends on the public endpoint and the network behavior of the endpoint at the time of the call, so it should be treated as a demonstration rather than an application dependency.

## Sending headers and request bodies through SQL

A request does not have to be limited to a URL and a body. The central `http()` function accepts an `http_request`, which allows callers to supply headers with `http_headers()`. The README's authorization example builds the tuple explicitly, passes `NULL` for content type and content, and then extracts a returned header from the response content:

```sql
SELECT content::json->'headers'->>'Authorization'
  FROM http((
          'GET',
           'http://httpbun.com/headers',
           http_headers('Authorization','Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'),
           NULL,
           NULL
        )::http_request);
```

The function returns a row-like response rather than only a body, so callers can inspect status and headers independently of content. The convenience methods support several payload styles. A GET can receive JSONB parameters, while POST can use JSONB as a form-style data payload. The README gives these calls:

```sql
SELECT status, content::json->'args' AS args
  FROM http_get('http://httpbun.com/get',
                jsonb_build_object('myvar','myval','foo','bar'));
```

```sql
SELECT status, content::json->'form' AS form
  FROM http_post('http://httpbun.com/post',
                 jsonb_build_object('myvar','myval','foo','bar'));
```

For PUT and PATCH, the body and content type are explicit strings. DELETE has no body argument in the documented shortcut. This API is convenient for text and JSON requests, but it does not document multipart form construction, cookie handling, proxies, retries or per-request timeouts.

## Binary payloads and response conversion in pgsql-http

The extension exposes response content as `varchar`, which creates a concrete limitation for binary HTTP payloads. The README explicitly warns that the ordinary `varchar::bytea` cast is unsuitable because it stops at the first zero-valued byte. For binary responses, callers are directed to use `text_to_bytea()` or `textsend()`.

The documented image example fetches a PNG, extracts the `Content-Type` header and measures the converted binary value:

```sql
WITH
  http AS (
    SELECT * FROM http_get('https://httpbingo.org/image/png')
  ),
  headers AS (
    SELECT (unnest(headers)).* FROM http
  )
SELECT
  http.content_type,
  length(text_to_bytea(http.content)) AS length_binary
FROM http, headers
WHERE field ilike 'Content-Type';
```

The reverse conversion is documented for binary request bodies: use `bytea_to_text` when sending `bytea` content with POST. This design is workable, but it adds an encoding and data-integrity boundary that ordinary database-native HTTP clients would need to handle differently. The README does not explain character-set edge cases, maximum payload sizes, streaming behavior, or what happens when the service returns data that cannot be represented safely through the extension's text conversion.

The response example also shows a practical parsing concern. The body must be cast explicitly, such as `content::json`, before JSON fields are accessed. A non-JSON response, malformed JSON body or unexpected content type can therefore move error handling into the surrounding SQL.

## HEAD requests, redirects and operational limits

HEAD requests are available through `http_head()`. The README states that they do not follow redirections, and its Google example returns status `301` together with a `Location` header. That behavior is useful when a caller wants to inspect redirect metadata, but it is not equivalent to a request that transparently reaches the final destination.

The same example shows how to join the response against an unnested header value. This is a reasonable PostgreSQL pattern, although it requires careful handling when the expected header is absent. A `LEFT OUTER JOIN LATERAL` can return a null value, whereas code that assumes every response includes `Location` will be brittle across services.

The project does not document request timeouts, connection reuse, retry policies, redirect configuration, TLS certificate controls, proxy settings, response-size limits, or behavior under a failed network request. The README's examples also use `http://` URLs, so they demonstrate request construction rather than a documented HTTPS policy. These omissions limit the confidence with which pgsql-http can be selected for latency-sensitive triggers or production workloads without additional operational investigation.

The natural alternative approach is to keep HTTP outside PostgreSQL and call the web service from an application, worker or ETL process. That separates network failure from the database connection, allows the application to use a maintained client library and makes retries, timeouts and observeability explicit. pgsql-http is the simpler choice only when placing the request directly in SQL or a trigger is an intentional requirement.

## pgsql-http releases, SQL upgrades and MIT licensing

The repository is not archived. Its last push was on 2026-07-09, and the listed releases include v1.7.2 on 2026-07-09, v1.7.1 on 2026-06-15, and v1.7.0 on 2025-07-24. This indicates recent repository activity based on the listed release history, while the README does not promise a maintenance cadence or compatibility policy.

The repository contains `http--1.7.sql` along with sequential upgrade files from earlier versions through 1.7. That layout indicates that PostgreSQL extension upgrades are represented as SQL version transitions. These files do not document the exact upgrade command, supported server versions, or whether every version transition has been validated for a particular distribution.

The repository identifies the project as MIT licensed through `LICENSE.md`. That is the relevant licensing fact for redistribution and integration review, but this article does not provide legal interpretation. A deployment still needs to account for PostgreSQL and libcurl terms separately, along with the policies of the HTTP services being called. The last push and release history should also be checked against the PostgreSQL version and build environment used in production.

## Conclusion

pgsql-http is appropriate for PostgreSQL users who need a narrowly scoped web request inside SQL, especially a trigger that must notify or query an HTTP service. It is a poor fit for binary-heavy transfers, redirect-sensitive HEAD workflows, or applications that require documented connection pooling, retry and timeout controls. Before adoption, verify that the packaged libcurl and PostgreSQL build environment matches the target server, then confirm how the extension will be loaded and secured. With those checks complete, the explicit SQL API and recent v1.7.2 release make it a credible choice for small database-to-service integrations.

## FAQ

### How do I install pgsql-http in PostgreSQL?

The README does not document installation commands, a package name, supported PostgreSQL versions, or an extension-creation statement. The repository includes a Makefile that uses `pg_config` and `curl-config`, but it does not provide a complete deployment tutorial.

### Which HTTP methods does pgsql-http support?

The documented utility functions include `http_get()`, `http_post()`, `http_put()`, `http_patch()`, `http_delete()` and `http_head()`. They wrap the central `http(http_request)` function and return an `http_response` composite.

### How does pgsql-http handle binary response content?

Binary content is returned through the text representation, so the README says to use `text_to_bytea()` or `textsend()` rather than the normal `varchar::bytea` cast. For binary request content, it documents converting `bytea` with `bytea_to_text`.

## Sources

- [Issues](https://github.com/pramsey/pgsql-http/issues)
- [License: MIT](https://github.com/pramsey/pgsql-http/blob/master/LICENSE)
- [pramsey/pgsql-http on GitHub](https://github.com/pramsey/pgsql-http)
- [README](https://github.com/pramsey/pgsql-http/blob/master/README.md)
- [Releases](https://github.com/pramsey/pgsql-http/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pramsey-pgsql-http
