Documentation Index

Fetch the complete documentation index at: https://cloudinary.com/documentation/llms.txt

Use this file to discover all available pages before exploring further.

VS Code extension analytics

Last updated: Oct-26-2026

Caution
These docs are strictly for internal use only. They are only present in the development (docs-dev) and staging domains. There should be no external links to these pages, and links to these pages should not be shared externally.

Overview

The Cloudinary VS Code extension reports usage data through three separate mechanisms:

  • UI events. The extension explicitly reports named events, such as library_opened or upload_succeeded, to the Cloudinary analytics endpoint. These events describe what a developer did inside the editor.
  • API call attribution. Every Admin API and Upload API call the extension makes carries a CloudinaryVSCode/<version> user agent, so extension traffic is identifiable in the standard API logs without any separate reporting.
  • Delivery URL analytics. Delivery URLs the extension generates carry an _a parameter that identifies the Node SDK rather than the extension, so it can't be used for extension attribution. See Delivery URL analytics.

The first two land in the same Snowflake data source, CLOUDINARY.LOGS.UDR_RAW, because the analytics endpoint is itself a Cloudinary API controller. The third rides on delivery URLs and is recorded in the CDN delivery logs.

Note
This page describes extension version 1.0.1. When you add, rename, or remove an event, update the UI event catalog in the same pull request.

How UI events are reported

UI events are sent by AnalyticsService in src/analytics/analyticsService.ts. A single instance is created during activation in src/extension.ts and passed to every command and webview provider that needs it.

Each event is one HTTP POST request, with the whole payload in the query string:

Requests are fire and forget. track() doesn't await the response, and any network failure is swallowed, so analytics can never block or break an extension workflow.

Parameters sent with every event

The analytics service attaches these parameters to every event, regardless of the event name.

Parameter Description
source Always vscode_extension. Also used as the endpoint path segment.
event The event name. See the UI event catalog.
extension_version The version field from the extension's package.json, for example 1.0.1.
ide_platform The detected editor: cursor, windsurf, antigravity, or github-copilot.
session_id A random UUID created on first use and stored in VS Code global state under cloudinary.analyticsSessionId.
event_time The client clock at report time, as an ISO 8601 timestamp.
cloud_name The active product environment, or the literal (not configured) when no cloud is configured.
debug_id Only present when the CLOUDINARY_ANALYTICS_DEBUG_ID environment variable is set. Used to isolate a specific developer's events during testing.

Note
ide_platform reports github-copilot for plain VS Code, and also for any editor the extension fails to recognize. It's a label for the AI tooling associated with the editor, not the editor name, so don't read it as a clean editor breakdown. The user agent on API calls carries the real editor name and version.

Payload rules

The analytics service normalizes and filters every event payload before sending it:

  • Event names must match ^[a-z][a-z0-9_]*$. An event with any other name is silently dropped, so it never reaches the endpoint.
  • Keys keep the spelling supplied by the caller. Reserved and sensitive key checks are case-insensitive and treat hyphens, dots, and whitespace as underscores.
  • Values are converted to strings. Only strings, finite numbers, and true or false values survive. Empty strings, null, undefined, NaN, Infinity, objects, and arrays are dropped.
  • Reserved keys are dropped so an event payload can't overwrite a common parameter: source, extension_version, ide_platform, session_id, event_time, and event.
  • Sensitive keys are dropped. Blocked names include credentials and tokens (api_key, api_secret, access_token, authorization, client_secret, password, refresh_token, secret, token, id_token), contact details (email, user_email, phone), and message content (prompt, message, message_content, user_message). Equivalent camel case and punctuation variants are also blocked.

Current analytics events don't include search text, Docs AI prompts, feedback comments, public IDs, URLs, or file paths. asset_search and message_sent report character counts instead. Docs AI sends conversation content to the Docs AI API as part of chat and feedback workflows; that traffic is separate from analytics reporting.

cloud_name and debug_id aren't reserved. An event-specific payload with either name replaces the corresponding common value.

Events reported from the Docs AI webview

The Docs AI view runs in a webview, which has no direct access to the analytics service. Its client code calls a local trackAnalytics() helper that posts an analyticsEvent message to the extension host, and DocsAiViewProvider._handleAnalyticsEvent forwards the name and payload to AnalyticsService.track().

The forwarding path doesn't use an allowlist of event names. The event name regex in the analytics service is the only guard, so any renamed event in the webview client reaches the endpoint under its new name without a matching change on the extension host side.

UI event catalog

Lifecycle and configuration

Event Reported when Event-specific parameters
extension_activated VS Code activates the extension. None.
config_validation_succeeded A credential check against GET /v1_1/<cloud>/config?settings=true returns 200 with a recognized folder mode. folder_mode: dynamic or fixed.
entry_point: activation, config_change, manual_refresh, or switch.
config_validation_failed The same credential check is rejected or unreachable. error_reason: unauthorized, unexpected_response, unexpected_status, or network_error.
status: the HTTP status code. Absent for network_error.
entry_point: as above.
config_opened The developer opens the global configuration file from the home screen. entry_point: homescreen.

Note
Credential checks that are skipped, because credentials are missing or are still the placeholder values, report nothing at all. There is no config_validation_skipped event, so config_validation_succeeded plus config_validation_failed doesn't add up to the number of activations.

Navigation

Event Reported when Event-specific parameters
home_opened The home screen is shown. entry_point: command for the cloudinary.showHomescreen command, or webview_ready when the webview reports that it finished loading.
library_opened The Library view is shown. entry_point: command, search when the command carried a search query, or webview_ready.
library_refreshed The developer runs the cloudinary.refresh command. entry_point: command.
docs_ai_opened The Docs AI view is shown. entry_point: command or webview_ready.
has_initial_prompt: true or false. Sent by the cloudinary.showDocsAI command and on webview ready.
has_initial_conversation: true or false. Sent by the cloudinary.showDocsAIConversation command and on webview ready.

Note
The first time a webview loads, opening it can produce two events: one when the command runs and one when the webview finishes loading. Count entry_point = 'webview_ready' separately from the command entry points.

Library

Event Reported when Event-specific parameters
asset_search A search is submitted. entry_point: library or homescreen.
query_length: the character count of the query. The query text itself is never sent.
resource_type: the active resource type filter. Sent from the library only.
library_view_changed The resource type filter or the sort direction changes. resource_type: the new filter.
sort_direction: the new sort direction.
asset_opened An asset preview is opened. entry_point: library, preview_command, or upload_result.
resource_type: image, video, or raw.
asset_type: the delivery type, for example upload, private, or authenticated.

Upload

Event Reported when Event-specific parameters
upload_widget_opened The upload panel is opened. entry_point: command, folder_context for the folder context menu, or library_context for the library folder tree.
folder_selected: true or false. Not sent for entry_point = 'command'.
upload_started An upload begins. upload_source: file or url.
folder_selected: true or false.
preset_selected: true or false.
upload_succeeded An upload returns successfully. upload_source, folder_selected, and preset_selected as above.
resource_type: the resource type Cloudinary assigned.
asset_type: the delivery type Cloudinary assigned.
upload_failed An upload throws. upload_source, folder_selected, and preset_selected as above.

Note
upload_failed carries no error detail, because the underlying error message could contain a file path or a signed URL. To investigate failures, join on the Upload API calls themselves, which do record status codes and error classes. See Attributing API calls to the extension.

Clipboard

Event Reported when Event-specific parameters
copy_asset_url An asset URL is copied. copy_type: secure_url, optimized_url, preview from the preview panel, or upload_result from the upload panel.
copy_asset_public_id An asset public ID is copied. copy_type: public_id.

Docs AI

These events originate in the webview client, src/webview/client/docs-ai/, and are relayed through the extension host.

Event Reported when Event-specific parameters
chat_started The first message of a new conversation is sent. conversation_id.
language: the browser locale of the webview, for example en-US.
message_sent Any message is sent. Fires alongside chat_started for the first message. conversation_id.
language.
message_length: the character count of the prompt. The prompt text is never sent.
is_first_message: true or false.
conversation_resumed A saved conversation is opened from the recent conversations list, chat history, or a deep link. Switching between open tabs doesn't report this event. conversation_id.
age_since_created_hours: hours since the conversation was created, to two decimal places.
age_since_last_message_hours: hours since the last message.
conversation_rated The developer rates an answer. rating: up or down.
star_rating: 1 to 5. Only sent with a down rating, and only when a star is chosen.
has_comment: true or false. Only sent with a down rating.
conversation_id.
copy_message A copy button on a code block or a message is clicked. copy_type: code or message.
code_length: sent for copy_type = 'code'.
copied_role: user or assistant. Sent for copy_type = 'message'.
copied_length: sent for copy_type = 'message'.
conversation_id.

Note
Clearing a thumbs up rating, or opening then dismissing the thumbs down form, reports nothing. Only a new thumbs up, or a submitted thumbs down form, produces a conversation_rated event, so ratings can't be netted off against retractions.

Attributing API calls to the extension

The extension doesn't report a separate analytics event for each Cloudinary API call. Instead, it identifies itself on the calls themselves, so API traffic is attributable using the standard API logs.

generateUserAgent() in src/utils/userAgent.ts builds the identifier:

For example, CloudinaryVSCode/1.0.1 (Cursor 1.99.3). The editor name is normalized to VSCode, Cursor, Windsurf, VSCodeInsiders, or VSCodeOSS, with a camel case fallback for anything unrecognized.

This identifier is applied in two places:

  • SDK calls. Activation and environment switching set cloudinary.utils.userPlatform. The Node SDK adds that value in front of its own user agent, so the header becomes CloudinaryVSCode/1.0.1 (Cursor 1.99.3) CloudinaryNodeJS/<sdk version> (Node <node version>).
  • Direct calls. The folder mode check in src/config/detectFolderMode.ts calls fetch directly and sets the User-Agent header itself.

In Snowflake, the header is available as HTTP_REQUEST_HEADERS:user_agent. The key uses an underscore, not the hyphen used on the wire.

The endpoints the extension currently calls are FoldersApiController/show, FoldersApiController/index, ResourceSearchApiController/index, ResourcesApiController/customer_configuration, UploadPresetsApiController/index, and UploadController/upload.

Delivery URL analytics

Delivery URLs the extension builds with cloudinary.url() carry an _a query parameter that the Node SDK appends automatically. The SDK's urlAnalytics option defaults to true and the extension doesn't disable it, so the optimized URLs and signed original URLs shown in the library and preview panels all include it.

_a encodes the SDK identity only: the SDK code, the SDK version, the language runtime version, and the product. It doesn't carry the extension version or the userPlatform string, so it can't be used to attribute delivery traffic to the VS Code extension. Extension attribution relies on the user agent described above.

Two consequences worth knowing:

  • A URL a developer copies out of the extension keeps its _a value, so once that URL is pasted into application code the delivery traffic is reported against the Node SDK rather than the extension.
  • Requests for these URLs are delivery traffic and are recorded in the CDN delivery logs, not in the API records that the rest of this page queries.

Where the data lands in Snowflake

The analytics endpoint is a Cloudinary API controller, so UI events flow through the standard usage data record (UDR) pipeline rather than a dedicated events table.

Source Contents Use it for
CLOUDINARY.LOGS.UDR_RAW One row per request, with the full query string in the HTTP_QUERY_PARAMS variant column. Anything that needs the event name or a payload value. This is the only source that retains them.
CLOUDINARY.AGG.DAILY_UDR_PARAMS_FINAL Daily aggregate, filtered with FEATURE = 'vscode_extension'. Cheap long-range request volume only.

UI events in UDR_RAW are identified by HTTP_QUERY_PARAMS:source = 'vscode_extension'. They resolve to ENDPOINT = 'AnalyticsController/collect', SERVICE_NAME = 'core', and, in the aggregate table, FEATURE = 'vscode_extension' with SOURCE = 'analytics'.

Note
The daily aggregate does not retain event names. PARAM_KEYS records only which parameters were present, so event appears as a bare key with no value, and true or false parameters are folded into the key itself as folder_selected:true. PARAM_VALUES holds a small allowlisted subset of values, currently source, resource_type, language, and status. Any analysis that needs to distinguish one event from another must query LOGS.UDR_RAW.

Known data quality caveats

  • Version 0.1.6 double reports. Version 0.1.6 sent a GET request with no event parameter alongside its POST. Filter on HTTP_REQUEST_METHOD = 'POST', or on HTTP_QUERY_PARAMS:event IS NOT NULL, or those rows inflate volume without being attributable to any event. Versions 1.0.0 and later send only the POST.
  • Test traffic. Events reported during development carry extension_version = 'local_test', or a debug_id parameter. Exclude both from adoption figures.
  • endpoint_check isn't a product event. It appears in historical data from the endpoint's initial verification and isn't reported by any current code path.
  • Client clocks. event_time comes from the developer's machine and can be wrong. UDR_RAW.TIMESTAMP is the server clock and is the safer choice for time bucketing.
  • Sessions aren't users. session_id is created once per installation and persists in global state, so it approximates an installation rather than either a user or an editor session. Cloudinary user IDs aren't reported with UI events, because the extension authenticates with API key and secret rather than as a Console user.
  • Asset previews can report twice. Opening an asset from Library or an upload result first reports asset_opened with its source entry point, then reports the same event with entry_point = 'preview_command' when the shared preview command runs.
  • Library folder uploads report twice. Opening the upload panel from the Library folder tree reports upload_widget_opened with entry_point = 'library_context', then reports it again with entry_point = 'folder_context' when the upload command runs.

Sample queries

These queries run against Snowflake directly and work unchanged in Redash.

Daily event volume by event name. The starting point for most questions:

Adoption by editor and version. Distinct installations and product environments per week:

View events by entry point. The command value covers both Command Palette actions and in-editor actions that invoke the same command. The webview_ready value identifies webview initialization:

Upload outcomes. Success rate split by upload source:

Configuration failure reasons. Useful when investigating setup friction:

API calls made by the extension. Attributed by user agent rather than by a reported event:

Tip
LOGS.UDR_RAW is a view over the full request log, so always include a TIMESTAMP filter. Queries that scan more than a few weeks without one are slow and expensive.

Long-range request volume from the aggregate. Cheaper than UDR_RAW, but with no event breakdown:

Adding a new event

  1. Pick a name that matches ^[a-z][a-z0-9_]*$ and follows the existing object_verb pattern, for example asset_opened, not openAsset.
  2. Report it through the shared analytics service. In commands and providers, call analytics?.track(). In the Docs AI webview, call the local trackAnalytics() helper.
  3. Keep the payload low cardinality and free of content. Report lengths, counts, flags, and enumerated values. Never report public IDs, URLs, file paths, search text, or prompts.
  4. Reuse existing parameter names. entry_point, resource_type, asset_type, and copy_type already have established value sets, and reusing them keeps queries simple.
  5. Add the event to the UI event catalog in the same pull request.
  6. Verify the event end to end by setting CLOUDINARY_ANALYTICS_DEBUG_ID to a unique value before launching the extension, then querying LOGS.UDR_RAW for that debug_id.

Resources

✔️ Feedback sent!

Rate this page:

one star two stars three stars four stars five stars