Skip to main content

Reference: exceptions

Throwing an exception from execute() or authorize() is how the code API surfaces errors. The framework translates each into a GraphQL error with a machine-readable extensions.code and a matching HTTP status. All built-in exceptions live in Automattic\WooCommerce\Api.

The base: ApiException

public function __construct(
string $message,
private readonly string $error_code = 'INTERNAL_ERROR',
private readonly array $extensions = array(),
int $status_code = 500,
?\Throwable $previous = null,
)

It extends \RuntimeException and exposes getErrorCode(), getExtensions(), and getStatusCode(). The controller merges your extensions with { code: <error_code> } (the code can't be overridden by an extensions entry), and uses status_code as the HTTP status.

Built-in subclasses

Each fixes a (code, status) pair; all share the signature ( string $message = <default>, array $extensions = [], ?\Throwable $previous = null ).

Classextensions.codeHTTPUse when
UnauthorizedExceptionUNAUTHORIZED401Authentication is required but missing; or a generic auth denial where re-authenticating might help.
InvalidTokenExceptionINVALID_TOKEN401Credentials were supplied but rejected (bad/expired token, malformed header).
ForbiddenExceptionFORBIDDEN403Authenticated, but lacks permission ("I know who you are, but you can't do this")
NotFoundExceptionNOT_FOUND404The resource doesn't exist. (When existence is sensitive, prefer UnauthorizedException to avoid leaking it.)
ValidationExceptionVALIDATION_ERROR422Input is well-formed but fails a business rule.

Other translated throwables

ThrownBecomes
\InvalidArgumentExceptionINVALID_ARGUMENT / 400 - use for malformed/structural input (wrong type, contradictory args).
any other \ThrowableINTERNAL_ERROR / 500 - message masked; the original is attached as previous and shown only in debug mode.

The framework also maps engine-level issues itself (e.g. an out-of-range Int output → BAD_USER_INPUT / 400; depth/complexity violations → 400).

Authorization-failure status

When an authorization gate denies (rather than throwing), the framework picks the status from the principal: 401 UNAUTHORIZED for anonymous principals (is_authenticated() is false), 403 FORBIDDEN for authenticated ones or principals that don't expose is_authenticated(). See Authentication and authorization.

Creating a custom exception (in a plugin or core)

Extend ApiException (or a subclass when its behavior fits) and pin your own code and status:

namespace Automattic\MyPlugin\Api;

use Automattic\WooCommerce\Api\ApiException;

class QuotaExceededException extends ApiException {
public function __construct( string $message = 'Quota exceeded.', array $extensions = array(), ?\Throwable $previous = null ) {
parent::__construct( $message, 'QUOTA_EXCEEDED', $extensions, 429, $previous );
}
}

Throw it from a command; the code and status_code surface automatically, and any extensions you pass appear alongside code in the response. Use a sensible standard HTTP status for your domain.