Skip to main content
OpenQASM 3 is a supported input format for IonQ jobs. Submit a job with "type": "ionq.qasm3.v1" and put your program in input.data, and our compiler will optimize it into IonQ native gates just as it does for circuits submitted in our JSON circuit format. IonQ supports a subset of OpenQASM 3, not the entire language. This page states exactly which constructs are accepted. Anything outside the subset is rejected, with an error naming the construct — your circuit will not be silently approximated.
This page is written for submitting jobs directly to the IonQ API. If you build your circuits in an SDK such as Qiskit or Cirq, the SDK constructs and submits the job payload for you and you do not need to write OpenQASM 3 by hand.
All of the restrictions on this page are checked during preflight validation, server-side, before your circuit is executed. Submitting is a separate step: the API accepts your request and returns a job ID, then the job moves to failed with the reason in its failure field. A circuit that fails validation never reaches a QPU and consumes no execution time.
Two restrictions apply on top of everything below. Neither is a language limitation — both are target capability checks, caught in preflight before execution.
  • Mid-circuit measurement is supported on some targets and not others. See Mid-circuit measurement.
  • Dynamic circuit execution — loops and branches — is not supported on any target, including the simulator. See Loops and branches.

Language version

Write OPENQASM 3.0; as your first statement. That is the conventional header and it is what every example on this page uses. The version number is not currently validated, however, and IonQ does not target a specific OpenQASM 3 minor revision. A program declaring OPENQASM 3.1; or OPENQASM 3.2; is neither rejected for being too new nor given access to anything extra, and a program with no header at all compiles the same way. The tables on this page are the whole answer to what IonQ accepts — the version header does not widen or narrow it. One consequence is worth stating explicitly: writing OPENQASM 2.0; does not put the compiler into an OpenQASM 2 mode. The header is ignored and the body is read as the same subset described here. In practice a simple OpenQASM 2 program often still compiles, because the subset accepts qreg, creg, and measure q[0] -> c[0]; alongside their OpenQASM 3 spellings. What it will not accept is a gate that is missing from the tables below — including much of qelib1.inc, such as ccx and u3 — regardless of which header the program declares.

Before you begin

You’ll need an IonQ Quantum Cloud account and an API key stored as IONQ_API_KEY. See our guide to managing API keys if you haven’t set one up.

Submitting a job

input.data holds the OpenQASM 3 program as a single string. The qubit count is read from the program itself, so there is nothing else to declare. This example prepares a Bell state:
Submit with "dry_run": true to compile your circuit without spending QPU time. Once the job reports "status": "completed", the compiled circuit is listed under output.compilation.compiled_circuits in several formats — including ionq.qasm3.v1, so you can read your compiled circuit back as OpenQASM 3.

Supported gates

IonQ recognizes the fixed list of gate names below. Anything else fails with Unknown gate: <name> unless you define it yourself.
include "stdgates.inc"; does not determine which gates are available. It is optional — your program is valid without it — and including it neither adds nor removes gates. It is accepted because SDK exporters such as Qiskit’s emit it automatically, so their output can be submitted unchanged.Do not read the include as a statement of support. Several gates that belong to stdgates.inc are not available on IonQ, including ccx, ch, cp, crx, cry, crz, cswap, and cu. The tables below are the authoritative list.

Single-qubit gates

Multi-qubit gates

Gate modifiers

A controlled gate must have exactly one target qubit, and only one modifier may be applied to a gate.
The ctrl @ modifier covers some of the missing controlled gates but not all of them — its base gate must be x, z, rz, xphase, or yphase, so ctrl @ h fails. For anything else, wrap your own decomposition in a gate definition.

Defining your own gates

The gate keyword works, and is the supported way to use an operation that isn’t in the tables above. Definitions are inlined at the call site:
Argument and qubit counts must match the definition exactly. def subroutines are a separate feature and are not supported.

Supported language constructs

Declarations

Measurement and reset

Every measurement needs its own classical bit. Assigning two measurements to the same bit is rejected:
Declaring several registers is the usual fix: bit[1] m; bit[2] c;.

Definitions and aliases

Barrier, timing, and directives

Expressions

Gate angles are resolved at compile time and must be constant.

Input and output parameters

OpenQASM 3 defines input and output directives for supplying parameters to a program and for naming the values it returns. Neither is supported. Both fail preflight validation as an unrecognized statement:
  • Instead of input, substitute the concrete parameter values into the program before submitting. Gate angles must be constant expressions in any case — see Expressions.
  • Instead of output, declare ordinary bit registers and measure into them. The job’s results report the final measured state of the qubits — see Reading results.

Reading results

Results do not come back in the OpenQASM program. Read them from the ionq.result.probabilities.json.v2 artifact, which a completed job lists in its results object from the Get job endpoint:
Fetch it by its id from the artifact endpoint. It returns the measured distribution keyed by bitstring — a 100-shot run of the Bell program above returned:
q[0] is the leftmost character of the bitstring. A three-qubit circuit applying x q[0]; and measuring all three returns {"100": 1.0}.
output_all is a fixed key holding the final state across every qubit in the circuit. Alongside it, each classical register you declare appears under its own name. The mid-circuit measurement example below declares bit[1] m; and bit[2] c; over two qubits, so its results carry three registers:
Since output_all reports the qubits rather than what you measured into, it can differ from the classical registers when a circuit measures mid-circuit, as it does here. The ionq.result.histogram.json.v2 and ionq.result.shots.json.v2 artifacts carry the same registers, as counts and as per-shot bit arrays. See Results formats for the full catalog of result artifact formats.

Mid-circuit measurement

Mid-circuit measurement (MCM) means measuring a qubit and then continuing to operate on other qubits. This is supported, but not on every target — it is a hardware capability, so availability depends on the backend you choose. The simulator supports it. Targets that do not will fail the job:
MCM circuits are valid OpenQASM 3 and compile successfully for every target, so this is caught by the target capability check in preflight rather than by the language validator. The job fails before execution and costs nothing. Use simulator unless you have confirmed your target accepts MCM. This example measures q[0], then continues working with q[1], giving each measurement its own classical bit:
Measuring mid-circuit is fine. Branching on the result is not — see Loops and branches.

Loops and branches

Dynamic circuit execution is not supported on any IonQ target, including the simulator, and is not planned for any live system. This covers if, else, while, switch, and for — including a for loop that does not depend on any measurement result.
These constructs are part of the OpenQASM 3 language and the compiler accepts them, so they are caught by the capability check in preflight rather than by the language validator. The job fails before execution and costs nothing. for loops are compiled into runtime loops rather than being unrolled, which is why an ordinary loop over gates is affected too. Unroll your loops and resolve your branches before submitting. Instead of:
write:
For reference, the compiler additionally rejects continue, end, for loops that do not start at 0 or step by 1, for loops over a discrete set, and while conditions using <, <=, >, or >=. Since none of these run in any case, this only matters when you are interpreting an error message.

Examples

Bell state

GHZ state

Toffoli

ccx is not a recognized gate name — use the control modifier instead:

Common errors

Every error below arrives the same way: the API accepts your submission and returns a job ID, then preflight validation fails the job before it executes. Read the job’s failure field to see which one you hit. Language and subset violations come back as PreflightError, with the specific problem in the message — for example, Circuit failed preflight validation: Unknown gate: ccx. These two are target capability checks rather than language errors, so they come back as UnsupportedFeature on a circuit that compiled successfully: