-
Notifications
You must be signed in to change notification settings - Fork 356
Deleting Extensions
This guide covers removing extensions from a registry — both open-vsx.org and a self-hosted private one. Deleting is irreversible, so read Delete vs. purge first.
- Delete vs. purge
- Who may delete what
- Finding what to delete
- Using the web UI
- Using the CLI
- Using the API directly
- Using the admin API
- Registries without a login provider
- Deleting directly in the database
These are two different operations and the difference matters:
| what happens | can the version be published again? | |
|---|---|---|
| delete | the version's files are removed, and its identity stays reserved | no, never |
| purge | the version is physically removed from the database and storage | yes |
Extension versions are otherwise immutable, so delete deliberately keeps the version number
claimed: nobody — including the original publisher — can later publish different content under a
version number somebody already installed. If you deleted a version by mistake and genuinely need
that number back, you need purge, which is admin-only.
Deleting through the regular API acts on behalf of the user owning the personal access token:
- namespace owners may delete any version in the namespace;
- other namespace members may delete only the versions they published themselves;
- anyone else may not delete anything.
Deleting an extension you don't own, or purging anything, requires a user with the admin role
and the admin API.
The CLI can't enumerate a registry - ovsx get --metadata needs an identifier you already have -
so use the API when you don't know what's published. Both endpoints are public, no token needed.
List the extensions in a namespace:
curl -s "https://openvsx.example.com/api/my-namespace" | jq '.extensions'List the versions of one extension:
curl -s "https://openvsx.example.com/api/my-namespace/my-extension/versions" | jq '.versions'To search across namespaces, use /api/-/search?query=... (see the OpenAPI docs at
/swagger-ui on your registry for its parameters).
If you can log in, this is the shortest route: open your extension's page, and each version you are allowed to delete offers the action there. Nothing else to install or configure.
The rest of this guide is for automating it, or for registries where nobody can log in.
ovsx unpublish needs a registry running 1.2.0 or later; it checks the registry version up
front and tells you if it's too old.
You need a personal access token; see Publishing extensions if you
don't have one. Set the registry and token once, or pass --registryUrl / --pat on each call:
export OVSX_REGISTRY_URL=https://openvsx.example.com
export OVSX_PAT=<your-personal-access-token>Delete an extension with all of its versions:
ovsx unpublish my-namespace.my-extensionDelete specific versions:
ovsx unpublish my-namespace.my-extension --versions 1.2.3 1.2.4Delete only certain target platforms of a version. --target requires --versions:
ovsx unpublish my-namespace.my-extension --versions 1.3.0 --target linux-x64 win32-x64Run from an extension folder and the identifier is read from package.json:
ovsx unpublishEach of these asks for confirmation first. In CI — or any non-interactive shell — it refuses
rather than prompting, so pass --force when you mean it:
ovsx unpublish my-namespace.my-extension --forceUseful when you'd rather not install Node, e.g. in a restricted environment. This is exactly what the CLI calls.
Delete the whole extension — note the allVersions parameter, without which nothing is deleted:
curl -X POST \
"https://openvsx.example.com/api/my-namespace/my-extension/delete?token=$OVSX_PAT&allVersions=true"Delete specific versions by posting a list. An entry without targetPlatform deletes every
target platform of that version:
curl -X POST \
-H "Content-Type: application/json" \
-d '[{"version": "1.2.3"}, {"version": "1.2.4"}]' \
"https://openvsx.example.com/api/my-namespace/my-extension/delete?token=$OVSX_PAT"Restrict a version to one target platform:
curl -X POST \
-H "Content-Type: application/json" \
-d '[{"version": "1.3.0", "targetPlatform": "linux-x64"}]' \
"https://openvsx.example.com/api/my-namespace/my-extension/delete?token=$OVSX_PAT"The response reports one line per deleted version, and nothing at all if there was nothing to delete — so an empty success means your selection matched no versions, not that it deleted everything.
For deleting extensions you don't own, and for purging. Both need a token belonging to a user with
the admin role. The request bodies are the same as above.
# delete (identity stays reserved)
curl -X POST \
-H "Content-Type: application/json" \
-d '[{"version": "1.2.3"}]' \
"https://openvsx.example.com/admin/api/extension/my-namespace/my-extension/delete?token=$ADMIN_PAT"
# purge (frees the version for republishing)
curl -X POST \
-H "Content-Type: application/json" \
-d '[{"version": "1.2.3"}]' \
"https://openvsx.example.com/admin/api/extension/my-namespace/my-extension/purge?token=$ADMIN_PAT"An admin can do the same from the web UI, under Admin Dashboard → Extensions
(/admin-dashboard/extensions), which offers both delete and purge per version.
A private registry deployed without any OAuth2 provider — an air-gapped cluster, say — has no way to log a user in, so there is no user to own a token and nothing can be deleted through any of the routes above. You can create the first user and its token directly in the database.
Connect to the registry's PostgreSQL database, then:
-- An admin user. Drop the role to create an ordinary publisher instead.
INSERT INTO user_data (id, login_name, role)
VALUES (nextval('user_data_seq'), 'openvsx-admin', 'admin')
RETURNING id;
-- A long-lived token for that user. Substitute the id returned above for <user-id>,
-- and a strong random string for the token value.
INSERT INTO personal_access_token (
id, user_data, value, active, version, type,
created_timestamp, accessed_timestamp, description
) VALUES (
nextval('personal_access_token_seq'), <user-id>, '<a-strong-random-token>', true, 0, 'LLT',
current_timestamp, current_timestamp, 'Admin API token'
);Three things are easy to get wrong here:
-
versionandtypeare required — both areNOT NULLwith no default (added inV1_72__Trusted_Publisher.sql). Omitting them fails withnull value in column "version" ... violates not-null constraint. Usetype = 'LLT'for a normal long-lived token;OTTandTPTare for one-time and trusted-publishing tokens, which the server issues itself. -
version = 0means "this value is still plaintext". Tokens are stored hashed. Inserting the raw token withversion = 0is correct and intended: the first request that uses it hashes it in place and bumps it to version1. Do not insert aversion = 1row with a plaintext value — it will never match. No restart is needed for the token to start working. -
Use the sequences rather than hardcoding ids.
user_data_seqandpersonal_access_token_seqare what the application allocates from, so a hardcoded id can collide with a row the registry creates later.
The role column accepts admin or privileged, and these are not interchangeable:
-
admingrants the admin API and dashboard — deleting, purging, managing users and namespaces. -
privilegedallows publishing to any namespace, bypassing ownership checks. It grants no admin access, so it cannot delete other people's extensions.
Leave role unset for an ordinary user, which is enough to publish to and delete from its own
namespaces.
Note that for local development you don't need any of this: the dev Gradle source set seeds a
super_user with the admin role and the token super_token
(server/src/dev/resources/db/migration). See doc/development.md.
Don't, unless you have no alternative. Extension data spans extension, extension_version,
file_resource and several dependent tables, storage holds files the database rows point at, and
the search index is updated by the application. Deleting rows by hand bypasses all of that and
leaves orphaned files and a stale index behind.
If you must, back the database up first. Note that this is also the one route that removes a version's reservation without a purge, which means content can later be republished under a version number that clients may already have cached.