Skip to content
qb
Menu
GitHub

v3.2.0 · C++20 · optional C++23 · Apache-2.0

Actors that share nothing. I/O that never blocks.

qb is a C++20 framework for concurrent and distributed systems on the actor model. An actor is thread-affine to one core, processes one event at a time, and talks only by message — so data races and deadlocks stop being possible rather than being avoided. It is also measured, in public, against the frameworks you would compare it to.

See the measurements →
greeter.cpp
#include <qb/main.h>
#include <qb/actor.h>
#include <qb/io.h>

struct Greeting : qb::Event {
    qb::string<64> text;
    explicit Greeting(const char *t) : text(t) {}
};

class Greeter : public qb::Actor {
public:
    qb::io::async::task<bool> onInit() final {
        registerEvent<Greeting>(*this);  // subscribe
        push<Greeting>(id(), "hello");  // send to self, in order
        co_return true;  // the actor is live
    }
    void on(const Greeting &e) {
        qb::io::cout() << e.text << '\n';
        kill();  // done
    }
};

int main() {
    qb::Main engine;
    engine.addActor<Greeter>(0);  // core 0
    engine.start();
    engine.join();  // until every actor has stopped
    return engine.hasError() ? 1 : 0;
}
qb-core
Actor engine — scheduling, mailboxes, lifecycle
qb-io
Async runtime: the qev loop, transports, coroutines
Standalone
qb-io is usable without the actor engine
3 platforms
Linux · macOS · Windows, on x86-64 and ARM64

Release 3.2.0 · 2026-09-21

A performance release, with no source break.

3.2 is a minor: code that compiles against 3.0 keeps compiling. What changed is what a message, an ask and an idle core cost. Each entry below was measured against the shipped 3.1.0 in the same quiet session, control beside candidate.

  1. An ask no longer allocates a frame

    co_await qb::ask(...) returns an awaitable that lives in the caller's coroutine frame: nothing is allocated for the exchange, the timeout is a deadline in the core's own clock, and the reply resumes the waiting coroutine on the spot. It is source-compatible, and still converts to task<E>.

    ask round trip: 45.2 → 35.6 ns on g++-14 · 64.0 → 48.5 ns on MSVC 19.51

  2. The actor object lives in a per-thread arena

    An actor is created and destroyed on one thread by construction, so its allocation needs no lock, no atomics and no cross-thread free path. It was the last heap traffic of an actor's life; larger or over-aligned types still fall through to the global allocator.

    fib, an actor per unit: 129.2 → 99.1 ns on g++-14 · 185.3 → 127.2 ns on MSVC 19.51

  3. An idle core parks inside its own I/O loop

    A core with latency above zero now waits where a wake can reach it: inside its event loop when it owns sockets or timers, on the mailbox otherwise, through a handshake that cannot lose a signal. On Linux 5.11 and later a parked core meets a timer in under a millisecond.

    ping-pong, two cores, park: 26.24 µs → 153 ns per round trip

  4. More toolchains, and one more compile-time refusal

    clang-cl is a supported Windows toolchain, and recovers about half of the distance between MSVC and g++. The io_uring backend is at parity with epoll. An event type wider than the cross-core mailbox ring is now refused by the compiler instead of being found at run time.

    qev 5.1.0 · epoll · kqueue · io_uring · wepoll

The contract

Five rules decide whether qb code is correct.

Everything else is detail. They come from the framework's own code-generation guidance, and they are the fastest way to understand what qb guarantees and what it asks of you in return.

  1. Time is std::chrono

    Every timeout, TTL, interval, delay and latency is a qb::duration — std::chrono::nanoseconds. It accepts finer-or-equal chrono literals and rejects bare integers at compile time. qb::mono_time and qb::wall_time are distinct types, so subtracting a steady clock from a system clock does not compile.

    qb::duration
  2. Actors share nothing

    An actor is thread-affine to one VirtualCore, processes one event at a time, and communicates only by events. No mutexes, no shared mutable state, and no actor is ever constructed outside a worker thread.

    qb::VirtualCore
  3. Sending is noexcept

    push, send and broadcast are noexcept, so a throwing event constructor or an allocation failure calls std::terminate. Events are relocated with memcpy: no member may point into its own storage, which is why a by-value std::string is never a valid event member. send additionally requires a trivially destructible event, and is unordered.

    push / send / broadcast
  4. A coroutine captures by value

    Everything it needs is captured before its first co_await, because the actor can be destroyed while the coroutine is suspended. After any suspension the only legal channel back is the context. Prefer Actor::spawn, bound to the actor's cancellation scope, over spawn_detached, and pass the lambda without a trailing ().

    Actor::spawn
  5. Register every event you handle

    Call registerEvent for each event type inside onInit(), which is itself a coroutine returning a task. Returning false or throwing fails creation and yields an invalid ActorId.

    registerEvent<T>(*this)

What it costs

Four things, honestly. None of them produces a compile error, which is exactly why the documentation exists.

  • An event is memcpy-relocated and its source destructor never runs, so a payload must be trivially relocatable, not merely copyable. C++20 has no trait for that.

    Documented in messaging.md →
  • The reference push returns lives until your handler returns. It survives further pushes, since the pipe is segmented and never moves an event, but not a co_await, and never a member.

    Documented in buffers.md →
  • Blocking the calling thread inside a handler freezes every actor on that core, with no diagnostic.

    Documented in async_in_actors.md →
  • The runtime commits memory in proportion to the square of the core count and never gives it back: about 4.5 MiB at rest on eight cores since 3.2, and 20–21 MiB once every pipe has carried an event.

    Documented in buffers.md →

Scope, stated plainly

What qb is not trying to be, so you do not have to find out by adopting it.

  • Actors live in one process. Other processes are reached through the transports and the modules.

  • There is no transparent remoting, no persistent mailbox and no supervision tree: a parent holds a child through addRefActor, and reads its handle only once it is ready().

  • Inside the process, delivery is in order per sender and receiver with push. With send it is unordered, and cheaper for a lone notice.

  • An event wider than the mailbox ring is a compile error: bulk data travels behind a pointer, not by value.

The engine

One worker thread per core, one mailbox each.

qb::Main launches a VirtualCore per engine core. Each worker owns its actors, drives exactly one event loop (sockets, timers, coroutines and your handlers in the same pass) and drains a private lock-free mailbox. The only genuinely multi-threaded surface in the whole system is the message-passing layer between cores, which is why placement is a decision you make, not one the runtime hides.

Event delivery across two VirtualCoresTwo worker threads. On the left, core 0 holds actors A, B and C; on the right, core 1 holds D and E. An event from A to B stays inside core 0's local pipe. An event from C to E crosses into core 1's multi-producer single-consumer mailbox, which only core 1 drains.VirtualCore 0VirtualCore 1ABCDElocal pipemailboxMPSCmailboxMPSCcross-core — a lock-free ring per producer
Same-core delivery never leaves the thread. Only cross-core delivery touches the ring.

Same core

The event is appended to the local pipe and consumed in the same loop iteration, after the current handler returns. No mailbox, no inter-thread synchronisation.

Across cores

A qb::ActorId is a service id and a core id in 32 bits, and the core half is the routing decision. The event is published into the destination core's mailbox, a sharded MPSC ring with one SPSC ring per producer core, and dequeued on its next pass. This is the only path that touches lock-free machinery.

Idle latency is a knob

A latency of zero never parks: the lowest possible latency, and one CPU fully occupied. Above zero, a core that has been idle for its idle-spin floor (50 µs by default) parks for up to that span: inside its own event loop when it owns sockets or timers, on the mailbox otherwise. A producer wakes it through a handshake that cannot lose the signal.

An actor's life

Constructed on its own core, gone on the same thread.

addActor reserves an id and returns; everything after that happens on the actor's own worker thread. A failed start is reported by Main::hasError(), never thrown.

  1. 01

    Constructed

    Built on its core's thread, then onInit() runs once. Returning false or throwing removes the actor before it processes a message.

  2. 02

    Activating

    If onInit() suspends, unicast events are stashed and replayed in order on activation; broadcasts and kills still arrive. An init that outlives its deadline, 5 s by default, is removed.

  3. 03

    Active

    One event at a time, in order per sender. Coroutines spawned by the actor belong to its cancellation scope.

  4. 04

    Gone

    kill(), or a KillEvent from anyone: handler calls already queued are skipped, the coroutine scope is cancelled, and the destructor runs on the same thread.

Measured

The fastest of three in every cell. The losses are named too.

The comparison lives in its own public repository: qb, CAF and SObjectizer on the Savina shapes, a suite chosen by neither side and older than qb. It is written and run by qb's maintainer, which is exactly why it is built to be checked by someone who wants it to come out the other way.

One build

Every framework is compiled from source, in one project, under one set of release flags. Nothing consumes a prebuilt package.

Pinned, and read back

Every framework's workers are pinned one per CPU through its own public API, and the pin is read back before a number is kept.

Verified, or no timing

Every cell reproduces a checksum computed in a framework-free header. A framework that loses one message in a million produces no timing at all.

A floor, not a rival

Raw std::thread and a hand-written SPSC ring mark what the hardware allows. It is not a framework, and it is never ranked as one.

One core, spin

Nanoseconds per unit of work: a round trip, a message or a hop, declared once in each benchmark's spec. WSL2 Debian 13 with g++ 14.2 and Windows 11 with MSVC 19.51 on one i9-12900K, pinned, nine repetitions and two warm-up, one quiet session per host on 2026-09-13. The bar under each qb figure is its share of the second-fastest framework's time.

One core, spin: nanoseconds per unit for qb, the second-fastest framework and the raw-thread floor, on two compilers
Shape (unit)g++ 14.2 · WSL2 Debian 13MSVC 19.51 · Windows 11
qbSObjectizerfloorqbSObjectizerfloor
ping-pongper round trip
22
1392
30
1822
countingper message
7
1053
10
1393
thread-ringper hop
17
703
18
919
fork-joinper message
8
1033
11
1395
bigper round trip
18
1337
17
18311

Two cores, the cells that decide a ranking

Nanoseconds per unit again, but each figure is a census: the median of twelve interleaved launch medians. Where qb sits under the floor it is not beating raw threads. The floor's ring pays one cache line per message where qb's producer publishes a batch.

Two cores: nanoseconds per unit for qb, CAF and the raw-thread floor
CellqbCAFfloor
ping-pongg++-14
155.9
279.6182.9under the floor
thread-ringg++-14
75.4
140.3103.1under the floor
ping-pongMSVC 19.51
186.8
475.8180.8
thread-ringMSVC 19.51
105.1
235.4112.4under the floor
ping-pongApple M4 Pro, unpinned
196.4
386.6237.7under the floor
ping-pongarm64 Linux guest
172.0
297.4207.1under the floor

Across all eight shapes and four hosts, qb is the fastest framework in every cell.

Geometric mean of qb over the best rival: the share of the best rival's time that qb takes, so lower is better. CAF's two-core cells run on one thread and still lose to a two-thread qb by 1.8–2.5×.

x86-64 hosts, g++ and MSVC
0.13
macOS, Apple M4 Pro
0.105
arm64 Linux guest
0.139

Where qb loses, named

Not against a framework, and not against its previous release. Against the floor, and against itself between compilers.

  • The one-core cells whose floor is a bare function call: qb pays 7–22 ns per unit for the mailbox, the pipe and the dispatch, 2.4–2.7× a floor of a few nanoseconds.

  • fib, an actor created and destroyed per unit, and bank-transaction, an ask round trip per transfer, sit at 3–4× their floors even after the actor arena and the frame-free ask of 3.2.

  • MSVC against g++ on the same source: 28 % slower at one core, half of which clang-cl recovers.

How to read these numbers

The benchmark repository sets its own limits, and a figure without its protocol is not a measurement.

  • Per shape, never as one ranking. Five of the eight shapes are shown here, and each is a different pattern of communication.

  • They are micro-benchmarks on at most two cores. They do not measure your application, nor correctness, safety or maturity.

  • Four hosts are two machines, and the Linux figures come from hypervisor guests (WSL2, and UTM on the Mac), not from bare metal. The macOS host is unpinned.

  • One version, one date: a comparison is a photograph. The repository is tagged qb-3.2.0-field so this one can be reproduced.

Messaging

Five primitives, five different contracts.

Actors communicate only by typed events. The primitives differ in ordering, in whether they construct a new event or redirect the one you received, and in what the runtime may do under backpressure. When unsure, the answer is push, and the reference it returns is valid until your handler returns.

Five primitives, five different contracts.
PrimitiveOrderingEvent objectNotes
pushFIFO per source→destNewThe default. Constructed in place at the tail of the destination pipe; returns a mutable reference so the sender can finish populating it, and the receiver runs the event's destructor.
sendNoneNewConstructed at the tail like push, but handed to the destination core's ring at once, so it carries no ordering. Requires a trivially destructible event; an EventQOS0 may be dropped under backpressure.
replyNone — goes through sendReusedSends the received event back to its source. The handler must take a non-const reference, because the event itself is what travels.
forwardNone — goes through sendReusedRedirects the received event to a new destination, preserving the original source. Same non-const handler.
broadcastNoneNewFans out to every actor on every core, one send per core, so it inherits send's lack of ordering.

In practice

A server, a client, an ask, a timer.

Network endpoints are actors with a mixin: I/O runs on the actor's own event loop and parsed messages arrive as ordinary handler calls. Request and reply is a free function and a co_await. Nothing is registered globally, and nothing blocks a core. Every excerpt is taken from a program that compiles and runs against this release.

qb-io

The runtime works without the actors.

qb-io is a single-threaded event loop with non-blocking transports, a message-framing protocol layer and a native C++20 coroutine layer. The loop is qev 5.1.0, a maintained libev fork with epoll, kqueue, io_uring and a real epoll on Windows through wepoll, published on its own. qb-core is built on qb-io, and qb-io can be taken alone. Application protocols like HTTP live in modules, not here.

Transports

  • TCP
  • UDP
  • SSL/TLS
  • QUIC
  • file

Protocols

  • text
  • binary8/16/32
  • JSON
  • MessagePack
  • AProtocol

Coroutines

  • task<T>
  • channels
  • generators
  • when_all
  • coroutine_scope
  • retry

Utilities

  • crypto & JWT
  • compression
  • URI
  • UUID
  • lock-free queues

Standalone

The runtime on its own

Every sample here is a plain executable that drives its own loop: no actor, no engine. That is the claim the section makes, so it is the one worth showing.

Modules

The protocols ship as modules, not as core.

Each is its own repository, fetched by CMake at the same ref as qb: the three modules and the examples ship in lockstep with the framework. They are compiled libraries built on qb-io, so they run on the same event loop as your actors and never block a core. None speaks its wire protocol through a third-party client.

qbm-http

Stable

Released as v3.2.0, in lockstep with qb

HTTP servers and clients as actors: a fluent router with path parameters, groups and controllers, a middleware pipeline (CORS, JWT, rate limiting, compression, static files, security headers), request validation, cookies and multipart, and both callback and coroutine client APIs.

Provides

  • HTTP/1.1
  • HTTP/2
  • HTTP/3
  • WebSocket
  • routing
  • middleware
  • validation
  • JWT
  • h2 auto-reconnect

HTTP/1.1, routing, middleware and validation are always built. HTTPS, HTTP/2, WebSocket and JWT need an SSL build; HTTP/3 additionally needs QUIC and libnghttp3. The gates are real #ifdef boundaries, so what your code can reach is what was compiled. Since 3.2 the HTTP/2 client reconnects under a RetryPolicy instead of at once and without bound.

Umbrella header
<qbm/http/http.h>
View repository →

qbm-pgsql

Stable

Released as v3.2.0, in lockstep with qb

The PostgreSQL v3 wire protocol spoken directly over a qb-io socket. The handshake, authentication (SCRAM-SHA-256 with channel binding, MD5, cleartext), the simple and extended query protocols, type encoding, COPY and LISTEN/NOTIFY are all implemented on the event loop.

Provides

  • no libpq
  • SCRAM-SHA-256
  • prepared statements
  • transactions
  • savepoints
  • COPY
  • cursor streaming
  • LISTEN/NOTIFY
  • cancel_async
  • typed results

timestamptz round-trips through qb::wall_time at integer-microsecond precision, and TLS is available on an SSL build. Since 3.2 cancel_async() sends the out-of-band CancelRequest without blocking the loop, and array decoding fails loudly instead of guessing at a NULL element. The client does not require actors.

Umbrella header
<qbm/pgsql/pgsql.h>
View repository →

qbm-redis

Stable

Released as v3.2.0, in lockstep with qb

A native RESP2 and RESP3 parser over a qb-io socket. Every command group is a mixin on the client: strings, hashes, sets, sorted sets, streams, geo, scripting, functions, ACL and the cluster commands.

Provides

  • no hiredis
  • RESP2 / RESP3
  • full command surface
  • pipelining
  • pub/sub
  • streams
  • scripting
  • cluster commands
  • auto-reconnect

RESP3 is opt-in with hello(3). Auto-reconnect uses exponential backoff, and a command deadline drops a connection whose reply never arrives. The cluster commands are wrapped, but the client does not follow MOVED or ASK redirects by itself. Like qbm-pgsql, it runs without actors if you drive the loop yourself.

Umbrella header
<qbm/redis/redis.h>
View repository →

qev

v5.1.0MIT

The event loop, published on its own: libev's API, maintained. It picks up where libev 4.33 stopped in 2020, with kqueue, epoll with epoll_pwait2, a rewritten io_uring backend and a real epoll on Windows through wepoll. qb embeds a copy held byte-identical with this repository, and qev versions on its own.

qb-examples

v3.2.0Apache-2.0

The runnable half of the documentation: about a hundred programs in seven tiers, from a first actor to whole applications built on the modules (99 on Linux, 97 on Windows). Each carries a checked header and an expected output, and the suite runs as part of the release gate.

In practice

What each one actually looks like

Every sample below is taken from the module’s own documentation, which is generated against its headers, so what you read here is what compiles.

qbm-http

One router, four transports. A handler written for HTTP/1.1 serves HTTP/2 and HTTP/3 unchanged, because every server feeds the same Router.

Choosing a client

The three persistent clients share the connect and push_request shape. These are the rows that decide which one you want.

Choosing a client
Capabilityhttp1http2http3
Compile gatenoneQB_HAS_SSLQBM_HTTP_HAS_HTTP3
Transport (ALPN)TCP / TLSTLS only (h2)QUIC (h3)
Base URI schemehttp:// or https://https:// onlyhttps:// only
Concurrencyone active, rest queuedmultiplexedmultiplexed

qbm-pgsql

The wire protocol, not a wrapper around libpq. Every call has a coroutine form and a callback form under the same method name.

qbm-redis

A native RESP parser and the full command surface, each group a mixin on the client. Redis errors arrive as a false reply, never as an exception.

Adding one to a project

One CMakeLists.txt, no submodule: qb and the module are fetched at the first configure, at the same ref. main is the released line; pin a vX.Y.Z tag in production. A module you keep in your own tree, under qbm/, is discovered by directory instead.

Fetch qb and a module
cmake_minimum_required(VERSION 3.24)
project(hello_http CXX)
include(FetchContent)
set(QB_BUILD_TESTS OFF CACHE BOOL "" FORCE)
FetchContent_Declare(qb        GIT_REPOSITORY https://github.com/isndev/qb.git        GIT_TAG main GIT_SHALLOW TRUE)
FetchContent_Declare(qbm-http  GIT_REPOSITORY https://github.com/isndev/qbm-http.git GIT_TAG main GIT_SHALLOW TRUE)
FetchContent_MakeAvailable(qb qbm-http)  # qb first: the module registers itself against it
add_executable(hello_http main.cpp)
target_link_libraries(hello_http PRIVATE qbm::http)
Or discover modules in your tree
qb_load_modules("${CMAKE_CURRENT_SOURCE_DIR}/qbm")  # the modules live under qbm/<name>
target_link_libraries(my_app PRIVATE qbm::http)

Writing your own

A qbm module is a contract rather than a private arrangement: the scaffold that produced these three is a script you can read, then run. It generates the target, the qbm:: namespace, a tiered test suite and a superbuild root that can run those tests.

Scaffold a module
curl -fsSL https://raw.githubusercontent.com/isndev/qb/main/script/qb-new-module.sh -o qb-new-module.sh
bash qb-new-module.sh mymodule  # a qbm module, run from your project's qbm/ directory

Install

CMake 3.24, a C++20 compiler, and one FetchContent.

Fetch it, embed it, or consume an installed prefix. Or let a generator write a project that is already wired to qb and its modules, with a test suite and a CI workflow.

FetchContent, no submodules
include(FetchContent)
FetchContent_Declare(qb GIT_REPOSITORY https://github.com/isndev/qb.git
                        GIT_TAG main)  # main is the released line; pin a vX.Y.Z tag in production
FetchContent_MakeAvailable(qb)
target_link_libraries(my_app PRIVATE qb::core qb::io)
A source tree, or an installed copy
add_subdirectory(qb)  # embed
target_link_libraries(my_app PRIVATE qb::core qb::io)

find_package(qb CONFIG REQUIRED)  # or consume a prefix built with QB_INSTALL=ON
target_link_libraries(my_app PRIVATE qb::core qb::io)
Scaffold a project: fetch the script, read it, run it
curl -fsSL https://raw.githubusercontent.com/isndev/qb/main/script/qb-new-project.sh -o qb-new-project.sh
bash qb-new-project.sh MyProject && cd MyProject
cmake -DCMAKE_BUILD_TYPE=Release -B build && cmake --build build --parallel && ctest --test-dir build
Build qb itself
git clone --recursive https://github.com/isndev/qb.git && cd qb
cmake -DCMAKE_BUILD_TYPE=Release -B build && cmake --build build --parallel
ctest --test-dir build --output-on-failure
Opt into what you deploy
cmake -B build \
  -DCMAKE_BUILD_TYPE=Release \
  -DQB_WITH_SSL=ON \
  -DQB_WITH_COMPRESSION=ON \
  -DQB_WITH_QUIC=ON \
  -DQB_BUILD_TESTS=ON

Build options

Every capability beyond the core is a flag, shown here with its default, so a build carries only what it uses. The gates are real #ifdef boundaries in the installed headers and propagate to your target.

QB_WITH_SSL
ON
SSL/TLS and crypto (OpenSSL); off by itself when OpenSSL is absent
QB_WITH_COMPRESSION
ON
Compression (zlib)
QB_WITH_QUIC
AUTO
QUIC and HTTP/3 through ngtcp2, on when found
QB_WITH_LOGGING
ON
Logging support
QB_BUILD_TESTS
ON
The framework test suite
QB_BUILD_BENCHMARKS
OFF
The Google Benchmark suites
QB_ENABLE_NATIVE_ARCH
OFF
-march=native, for host-local builds only

The event loop, stduuid, nanolog and the flat hash map are bundled. nlohmann/json is a real dependency, because qb::json is nlohmann::json: a system copy of 3.11 or later is used when found, a pinned tag fetched otherwise.

Platform support

  • Linuxx86-64 · arm64

    GCC 14 · Clang 19 and 22 · epoll · io_uring opt-in

    Hosted CI on every push: GCC 14, Clang 22 at C++20 and C++23, and a Clang 19 lane; epoll and io_uring.

  • macOSApple Silicon · x86-64

    Apple Clang · kqueue

    Hosted CI on every push.

  • Windowsx86-64

    MSVC 19.5x · clang-cl · wepoll over IOCP

    The maintainer's gate before each release. The hosted CI job is disabled on purpose, and INSTALL.md says why.

Thread pinning is best-effort: a refused pin only warns, and on Apple Silicon nothing is pinned. Branch on qb::CPU::ThreadPinningSupported(), never on the call returning.

Documentation

Eight sections, from the foundations to production.

The book is written in order: what the model is, then each library on its own, then the two together, then the task-oriented material and the reference. Foundations sits outside that order: the vocabulary qb adds to the standard library, and the machinery underneath.

  1. FoundationsTime vocabulary · the pipe · containers · encoding · lock-free primitives · ABI and the build fingerprint
  2. IntroductionOverview · philosophy
  3. Core conceptsActor model · event system · concurrency · threading model · async I/O
  4. qb-ioTransports · protocols · coroutines · SSL · QUIC · utilities
  5. qb-coreActor · engine · messaging · the pattern library: ask, ask_stream, ask_all, require
  6. Core and I/O togetherAsync inside actors · network actors · worked examples
  7. GuidesGetting started · patterns cookbook · performance tuning · error handling · resource management · production checklist · migration
  8. ReferenceAPI overview · building · CMake options · core and I/O invariants · benchmarks · testing · glossary · FAQ
Browse the full guide

Two pages worth opening first

For coding agents

llms.txt is the index and the five rules that decide whether generated qb code is correct; llms-full.txt adds the mental model and the deterministic API reference. Both are generated from the repository and checked in CI. Over MCP there is nothing to host.

MCP client configuration
{ "mcpServers": { "qb": { "url": "https://gitmcp.io/isndev/qb" } } }

Contact

Talk to the maintainers.

Questions about adopting qb, a module you need, or a production deployment. Bugs and feature requests belong in GitHub issues.

Open an issue

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.