Skip to content

PostgreSQL Logical Decoding Plugins: pgoutput vs wal2json

Learn how PostgreSQL logical decoding plugins work. Compare pgoutput vs wal2json vs decoderbufs for CDC pipelines, with examples and implementation tips.

Author
Ruben Burdin · Founder & CEO
Published
September 6, 2025
Read time
7 min read
PostgreSQL Logical Decoding Plugins: pgoutput vs wal2json
DATA ENGINEERING

When building change data capture (CDC) pipelines with PostgreSQL, the output plugin you choose determines how database changes get formatted and delivered—whether you're replicating to another PostgreSQL instance, streaming to Kafka, or pushing to a webhook.

The most common decision is wal2json vs pgoutput: both handle PostgreSQL logical decoding, but they serve different integration patterns. This guide covers all major output plugins, their technical characteristics, and practical guidance on choosing the right one for your architecture.

A Simple Example: See an Update Flow End-to-End

To understand how different output plugins work, let's start with a concrete scenario. We'll set up logical replication, make a change, and see how different plugins format that change.

Setting Up Logical Replication

First, enable logical replication in PostgreSQL by checking your WAL level:

SHOW wal_level;

If it's not set to 'logical', configure logical replication for your PostgreSQL provider (AWS RDS, Google Cloud SQL, Azure).

Create a Table and Replication Slot

`-- 1. Create a test table CREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT NOW() );

-- 2. Set replica identity for complete change tracking ALTER TABLE users REPLICA IDENTITY FULL;

-- 3. Insert initial data INSERT INTO users (name, email) VALUES ('John Doe', 'john@example.com');

-- 4. Create a publication CREATE PUBLICATION user_changes FOR ALL TABLES;

-- 5. Create logical replication slot with test_decoding plugin SELECT pg_create_logical_replication_slot('test_slot', 'test_decoding');`

Make the Change We're Tracking

-- The change we'll observe across different plugins UPDATE users SET name = 'John Smith' WHERE id = 1;

Reading Changes from the Replication Slot

-- View the formatted output SELECT lsn, xid, data FROM pg_logical_slot_peek_changes('test_slot', NULL, NULL);

This simple update produces dramatically different output depending on your chosen plugin—a critical factor for automated data sync between applications and integration complexity.

How Changes Flow from Table to Output Plugin

Understanding the data flow helps optimize your real-time data synchronization architecture:

1. Write-Ahead Log (WAL) Storage

PostgreSQL's WAL stores low-level binary records, not human-readable messages:

# Simplified WAL record structure WAL Record: LSN 0/1A2B3C4 - Relation OID: 16384 (internal table identifier) - Transaction ID: 12345 - Operation: UPDATE - Block/offset: physical storage location - Old tuple: [binary data for old row] - New tuple: [binary data for new row]

The WAL contains only internal identifiers and binary data—no table names, column names, or readable values.

2. Logical Decoding Process

This architecture allows PostgreSQL to support many output formats without changing the underlying WAL format or storing duplicate information. The core database only needs to log changes once in the WAL, and then any number of output plugins can decode those logs and present the data in JSON, SQL, binary, etc., as needed.

The decoding process:

  • 01Reads WAL records sequentially from slot position (LSN)
  • 02Resolves internal identifiers using system catalogs
  • 03Transforms binary data into logical representation
  • 04Assembles complete transactions in commit order
  • 05Passes structured data to the output plugin

3. Plugin-Specific Formatting

Every logical decoding plugin receives the same core information about the change; what differs is how they output it. The test_decoding plugin formats this as human-readable text, wal2json converts it to JSON, and pgoutput encodes it in PostgreSQL's binary logical replication protocol.

Each plugin receives identical decoded information:

  • Table name: public.users
  • Operation: UPDATE
  • New values: {id: 1, name: "John Smith", email: "john@example.com"}
  • Old values: {name: "John Doe"}

This standardized input enables consistent behavior across different output formats while supporting diverse integration requirements.

Built-in Output Plugins

PostgreSQL ships with two logical decoding plugins out of the box. These don't require any additional installations—they're ready to use on any Postgres 10+ server.

pgoutput Plugin

pgoutput is PostgreSQL's default plugin for logical replication. If you're using the built-in publish/subscribe system, you're already using this plugin behind the scenes.

Sample output (conceptual representation):

BEGIN LSN: 0/1A2B3C4 TABLE: public.users UPDATE: id[integer]=1 name[text]='John Smith' (old: 'John Doe') email[text]='john@example.com' COMMIT LSN: 0/1A2B3C4

The actual output uses a binary protocol requiring specialized parsing tools.

Technical characteristics:

  • Binary format provides efficiency and compactness
  • Handles complex PostgreSQL data types without information loss
  • High performance with incremental streaming
  • Production-ready, universally supported on managed services
  • Requires specialized tools for consumption (can't use SQL functions directly)
  • Binary protocol isn't human-readable for debugging

test_decoding

PostgreSQL's example plugin, primarily useful for understanding logical decoding mechanics and debugging.

Sample output:

BEGIN 12345 table public.users: UPDATE: id[integer]:1 name[text]:'John Smith' email[text]:'john@example.com' COMMIT 12345

Technical characteristics:

  • Human-readable text format for easy interpretation
  • Excellent for learning logical decoding concepts
  • Included with PostgreSQL by default
  • Useful for debugging replication issues
  • Output format not designed for production parsing
  • Limited functionality compared to specialized plugins
  • No advanced features like filtering or sophisticated type handling
See real-time two-way sync in action
Book a demo with real engineers — no sales script.
Book a demo

Choosing the Right Plugin

Your choice depends on environment constraints and performance requirements.

Output Plugin Comparison

PluginBest Use CaseTrade-offs
pgoutputPostgreSQL-to-PostgreSQL replication, high-volume CDC with managed servicesBinary format requires specialized parsing tools, not human-readable
test_decodingLearning logical decoding, debugging replication issuesNot production-ready, limited features, text parsing unreliable
wal2jsonExternal integrations, Kafka streaming, webhook deliveryHigher overhead at scale, slower with large transactions
decoderbufsHigh-throughput non-PostgreSQL targets, Debezium pipelinesSelf-hosted only, requires protobuf compilation, added complexity
wal2mongoDirect PostgreSQL to MongoDB replicationSpecialized use case, limited community support
decoder_rawSQL statement generation for heterogeneous databasesNiche scenarios, requires target database compatibility

Key Takeaways

pgoutput is the default choice for managed PostgreSQL services and delivers the best performance for most CDC workloads.

Avoid test_decoding in production—it lacks filtering, type handling, and reliable parsing for automated pipelines.

Choose wal2json when integrating with non-PostgreSQL systems; use decoderbufs for self-hosted high-throughput needs.

Environment Constraints

Managed Services (AWS RDS, Google Cloud SQL, Azure): The wal2json vs pgoutput decision here is usually straightforward—pgoutput for Postgres-to-Postgres replication subscribers, wal2json for external integrations and application-layer consumers.

Self-hosted environments: You have full flexibility. Consider decoderbufs for high-performance scenarios or stick with pgoutput for simplicity.

Performance Requirements

For high-volume real-time data synchronization scenarios:

  • 01pgoutput: Best overall performance for most use cases
  • 02decoderbufs: Optimal for high-throughput non-PostgreSQL targets (self-hosted only)
  • 03wal2json: Convenient but potential bottleneck at scale

Beyond Custom Logical Decoding Implementations

While logical decoding plugins provide the foundation for change data capture, building production-ready consumers requires significant engineering investment. Organizations implementing automated data sync between applications often face challenges that go beyond plugin selection.

Traditional logical decoding implementation challenges:

  • Custom consumer development and maintenance
  • API rate limiting and error handling
  • Infrastructure scaling and monitoring
  • Security and compliance implementation
  • Bi-directional sync complexity and conflict resolution

Modern data synchronization platforms address these challenges by providing real-time, bi-directional data synchronization that propagates changes between connected systems in milliseconds. When a record is updated in your CRM, the change is reflected quickly in your production database, and vice-versa. This helps ensure that all systems share a single, consistent state, reducing data integrity issues.

Ready to reduce integration complexity? Explore how Stacksync delivers real-time, bi-directional synchronization without heavy infrastructure or brittle pipelines.

FAQ

Frequently asked questions

What is the difference between pgoutput and wal2json plugins?
pgoutput uses a binary protocol optimized for PostgreSQL-to-PostgreSQL replication with better performance, while wal2json outputs human-readable JSON format that's easier to parse in any programming language but has higher overhead at scale.
Which logical decoding plugin should I use for Kafka streaming?
For Kafka streaming, wal2json is often preferred because its JSON output integrates easily with Kafka consumers. For high-throughput scenarios on self-hosted systems, decoderbufs with Protocol Buffers offers better performance.
Can I use logical decoding plugins on managed PostgreSQL services like AWS RDS?
Yes, managed services support specific plugins. AWS RDS, Google Cloud SQL, and Azure support pgoutput and wal2json. Self-hosted installations have full flexibility to use any plugin including decoderbufs.
How do I enable logical replication in PostgreSQL?
Set wal_level to 'logical' in postgresql.conf, then restart PostgreSQL. Create a publication for your tables and a replication slot with your chosen output plugin using pg_create_logical_replication_slot function.
What is REPLICA IDENTITY FULL and when should I use it?
REPLICA IDENTITY FULL ensures PostgreSQL logs complete old row values for UPDATE and DELETE operations. Use it when downstream consumers need before-and-after values for change tracking, auditing, or bi-directional sync scenarios.

About the author

Ruben Burdin
Founder & CEO

Ruben Burdin is the Founder and CEO of Stacksync, the first real-time and two-way sync for enterprise data at scale. Ruben is a Y Combinator alumni with a strong background in software engineering and business.

All posts by Ruben Burdin

About Stacksync

Stacksync powers real-time, two-way sync between CRMs, ERPs, and databases. Engineers sync data at scale and automate workflows — not dirty API plumbing.

Coworkers laughing in front of a laptop in a casual office setting

Your last integration took months.
Your next one takes a prompt.