# Native QTI Support

**URL:** <https://discuss.openedx.org/t/native-qti-support/19765>\
**Category:** Collaborative Proposals\
**Created:** [September 28, 2026, 4:21am UTC](https://discuss.openedx.org/t/native-qti-support/19765 "2026-09-28T04:21:22Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![dave](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/dave/32/263_2.png) [@dave](https://discuss.openedx.org/u/dave)\
**Post date:** [September 28, 2026, 4:21am UTC](https://discuss.openedx.org/t/native-qti-support/19765/1 "2026-09-28T04:21:22Z")

</div>

# Summary

I built a proof-of-concept XBlock that lets you use QTI directly in Open edX Platform. I believe that this is a valuable feature, and that the basic architecture I’ve laid out for this is workable. However, I do not have the time to actively develop and maintain this functionality. I am sharing this in the hopes of finding one or more interested maintainers who are enthusiastic to take this on.

# Context

[QTI](https://www.1edtech.org/standards/qti) is a widely supported 1EdTech standard for assessments. When people talk about assessments in the context of [Common Cartridge](https://www.1edtech.org/standards/cc), they’re talking about QTI. There are definitely rough edges when it comes to interoperability, but QTI is as close to a ubiquitous assessment format standard as we have in ed tech today.

Open edX platform’s `ProblemBlock` XML format was modeled after [LON-CAPA](https://www.lon-capa.org/), which never gained as much popularity as QTI. Up until now, our approach to QTI support has been to convert it into our native OLX format using the aptly named [cc2olx](https://github.com/openedx/cc2olx) tool. This works, but there are always going to be compromises because the models do not precisely align to one another. To my knowledge, we also don’t currently have a way to export things back out to QTI.

# Approach

The code is in my personal [pyqti](https://github.com/ormsbee/pyqti) repo. This repo has:

- The implementation of a QTI 3.0 Python library (using [xsdata](https://xsdata.readthedocs.io/en/latest/) to build models from the official QTI schema docs)
- An XBlock installed as an extras package with `pyqti[xblock]`
- A Tutor plugin that exists in a `tutor_plugin` directory.

The [QtiAssessmentItemBlock](https://github.com/ormsbee/pyqti/blob/6dbbe750c9d2d796d559ac6f97ed4a12f2833d76/src/pyqti/xblock/block.py#L183) works by having a wrapper `<openedx-qti>` tag that has a `<qti-assessment-item>` tag as its only child. OLX-specific attributes that our platform expects get put in the wrapper tag, while the `<qti-assessment-item>` can be directly copy-pasted in from existing QTI content.

The frontend rendering is entirely done by [Citolab’s qti-components](https://github.com/Citolab/qti-components). The `QtiAssessmentItemBlock`’s primary job is to take the source QTI XML, redact the answers from it, pass the renderable version of that XML to qti-components for rendering, and know how to communicate with it to check answers.

# Screenshots

 ![Add "openedx-qti" to your "Advanced module list" in Advanced Settings](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/0/04b12c61b3e7c877342283f5e2e2b18b3d7ac5e8.png)

 ![Add advanced component and create a "QTI Item"](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/f/f9956aa8ddf10699af9ee2c008df67cbd12602bd.png)

 ![The editor is XML source-only.](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/7/780f68f24b154bf8b5ec6ca7b5c576924f7746d2.png)

 ![Studio View](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/9/96c94254decb1deac858d04c4b73831a72d30068.png)

 ![LMS View](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/c/cbab6d89462292b43ea923a66dedc09aeffe33e7.png)

# How to Try it Out

I haven’t pushed any of this to PyPI, so you have to clone my [pyqti](https://github.com/ormsbee/pyqti) repo. The Tutor Plugin instructions are [in the README](https://github.com/ormsbee/pyqti/blob/main/README.md#developing-in-tutor), but I’ll echo them here:

```sh
pip install -e tutor_plugin # into the environment tutor runs from
tutor plugins enable pyqti
tutor mounts add /path/to/pyqti
tutor images build openedx-dev
tutor dev start -d

```

(I’ve only ever run this on tutor-dev.)

Note for those brave souls who run on bare metal: you need to install `pyqti[xblock]`. I’m trying to keep the XBlock layer cleanly separated, because the vast, vast majority of this code is XBlock-agnostic, and I think a general Python QTI library would be useful.

# Sample Data

I made a small course that you can import to see a few problems and try things out:  
[qti-test-course.tar.gz](https://discuss.openedx.org/uploads/short-url/d3Xl7j0U3QVjn3qtD3IVjnpNjv.gz) (5.5 KB)

# Status and Next Steps

This is very much a prototype. There’s a lot of LLM code generation that went into this, and my code reviews were mostly aimed at ensuring that it respected certain architectural boundaries, rather than being a close, line-by-line reading. The frontend code has some hacky bits around the CSRF token, and it bypasses bundling altogether by hitting an external CDN for the qti-components JS. There are many unimplemented features, including important things like being able to render images and custom stylesheets.

I do believe that the high level architecture will work, but it is definitely NOT something I’d put into production today. I wanted to prioritize getting a working proof-of-concept out for discussion.

If you or your organization is interested in pursuing this, please reply to this thread. I’d love to chat. 😁
