> For the complete documentation index, see llms.txt.
Skip to main content

Check out Port for yourself โžœย 

Visualize Cursor metrics

Implement with AI

Send this guide to your coding agent.

Prerequisite: Install Port MCP

Open plan mode. Implement this Port guide in my org via MCP:

https://docs.port.io/guides/all/visualize-cursor-metrics

Goal: get the guide's core flow working end-to-end in my org; adapting it to fit my existing setup takes priority over matching the guide 1:1.

Plan:
1. Confirm MCP is connected, in the right org, with sufficient permissions.
2. Diff the guide's data model (blueprints, properties, relations, actions, agents, automations, integrations, secrets) against mine.
3. Propose adaptations for gaps, reusing existing blueprints/relations over guide-named duplicates.
4. Flag what needs a UI click, credential, or secret from me, testing MCP capability empirically before ruling anything out.
5. Stop on any blocker and give me options. Approving this plan authorizes the writes it lists; pause only for writes beyond what's listed.

Build:
- Extend blueprint schema additively when upserting; don't remove or overwrite existing properties, and treat type conflicts as a blocker, not an auto-fix.
- List any mock data in the plan, minimal and labeled mock; once approved, seed it without re-asking, and tell me what you seeded.
- For anything the guide writes downstream (e.g. a webhook target), use a real entity, not a mock.
- For pages/widgets, use the real page identifier from the app URL, not a guessed slug.
- When you hit a UI step confirmed (not assumed) unsupported via MCP, pause, give exact clicks, then resume via MCP.
- Validate and give links after each meaningful step (only a tool-returned URL, no guessed paths); don't proceed if the last run wasn't a success.

Done:
- Confirm the guide's expected output exists and runs in Port.
- Summarize adaptations, seeded data, what was mocked or skipped, remaining UI steps, and how to verify.
Supported integration

This guide is scoped to Cursor. If you also operate other AI coding tools (e.g. GitHub Copilot, Claude Code), you can layer them on top of the same foundations described here. See Extending to other AI tools at the end of the guide.

Engineering organizations are investing heavily in AI coding assistants, but most cannot answer basic ROI questions: how many licensed seats are actively used, which teams are getting value, and whether suggestions are being kept or thrown away? Without per-user and per-team visibility, AI tooling budgets grow based on enthusiasm rather than evidence.

This guide walks you through building a comprehensive Cursor adoption dashboard in Port. We will set up the data model, connect the native Cursor integration to ingest live usage data, and build a dashboard on top of it. Once the integration is running and data is flowing, you can answer questions at three levels. The questions below are examples to get you started, not an exhaustive list:

  • Organization: What share of Port users actively use Cursor, what is the acceptance rate, and how many seats are inactive?
  • Team: Which teams are leading or lagging on Cursor adoption, acceptance, and LOC efficiency?
  • User: Which contributors are driving accepted lines of code, which users have low engagement, and which Cursor users have zero output?

These three levels map to Port's Organization, Team, and User blueprints, but the structure is flexible and can mirror however your own organization is shaped, for example nesting teams into departments or business units. See example organization hierarchies for the patterns you can model.

By the end of this guide, you will have a dashboard that provides full visibility into Cursor adoption and impact, helping you identify enablement opportunities and quantify ROI.

Cursor AI Adoption dashboard in Port showing headline KPIs, adoption tier distribution, and team-level metrics

The image above shows only the top portion of the dashboard as an example. The full dashboard built in this guide includes additional team, user, and activity-trend widgets further down.

Common use casesโ€‹

  • Track Cursor license utilization (active vs inactive seats) across the organization.
  • Surface low-engagement Cursor users who may benefit from enablement sessions.
  • Compare Cursor adoption rates and acceptance rates across teams to identify outliers.
  • Monitor daily Cursor activity, LOC trends, and acceptance trends over time.
  • Identify "Cursor active but zero output" users whose suggestions are not landing in code.
  • Correlate Cursor adoption with engineering outcomes such as PR delivery metrics, DORA metrics, or pipeline reliability to see whether higher AI usage actually improves delivery.

Modeling Cursor data in Portโ€‹

Port lets you visualize and model Cursor data based on your organization's structure. Usage is attributed to your User, Team, and Organization entities and enriched with derived metrics such as adoption rate, adoption tier, acceptance rate, and LOC efficiency, so you can compare teams, drill into individual users, sort by service ownership, and feed scorecards.

You can also chart the raw daily Cursor records (cursorDailyUsage) directly whenever you want day-by-day trends or totals across every seat reported by Cursor. By default these records are not related to your Team, service, or other catalog entities, so you cannot group them by those attributes unless you extend the integration mapping to relate the records to them.

The dashboard built in this guide uses both. The headline KPI tiles and team/user tables are attributed to your catalog entities, while the day-by-day activity-trend charts at the bottom chart the raw Cursor records.

Assign Port users to an AI profile

We recommend assigning every Port user who uses an AI coding tool to their matching AI tool profile (such as Cursor). This is what lets you model Cursor data on your organization's structure: it lets you understand AI adoption at the team level, see how usage breaks down across teams and services, build scorecards that grade teams against adoption targets, and trigger workflows based on usage (for example, flagging inactive seats or low-engagement users). Without it, you are limited to raw daily trends and totals, and lose the ability to compare and act on usage across your organization.

Prerequisitesโ€‹

This guide assumes the following:

Key metrics overviewโ€‹

The tables below are examples of metrics worth tracking at each scope. They are not exhaustive: the data model in this guide can produce many more, and you should add or drop metrics to match the questions your organization actually wants answered. Treat each list as a starting point for the widgets you build later.

MetricWhat it measuresWhy it matters
Days active (monthly)Number of days the user had any Cursor activityHeadline engagement signal for an individual
Adoption tierNone / Low (1-5 d) / Medium (6-15 d) / High (16+ d)Bucket users for enablement, recognition, or license review
Acceptance rateAccepted applies / total appliesWhether the user keeps the changes Cursor proposes
Tab acceptance rateTab completions accepted / shownAdoption and usefulness of inline tab completions
LOC efficiencyAccepted lines / suggested linesHow much of what Cursor proposes lands in their code
LOC added (monthly)Sum of Cursor-accepted lines added this monthConcrete contribution Cursor made to this person's code
Chat / Agent daysDays the user engaged Chat or Agent featuresAdoption of features beyond inline completion
How to read adoption rate

Adoption rate is a penetration metric: active Cursor users divided by all members, not just those who hold a seat. A low number can also signal incomplete ai_tools linking rather than low usage. See the FAQ for how to interpret it and why the denominator is whole-headcount.

Set up data modelโ€‹

Throughout this guide, blueprint and property titles (what you see in the Port UI) are written in bold, and their identifiers (what you use in JSON and integration mappings) in code formatting - for example, the AI User Profile blueprint has the identifier ai_user.

The Cursor integration automatically creates four blueprints when installed. This guide builds on the first:

  • Cursor Daily Usage (cursorDailyUsage) - daily per-user Cursor activity records.
  • Cursor Usage Event (cursorUsageEvent) - per-event model, token, and cost detail.
  • Cursor Team Model Usage (cursorTeamModelUsage) - daily team-level model breakdown.
  • Cursor User Model Usage (cursorUserModelUsage) - daily per-user model breakdown.

In this guide, we will:

  1. Create a new blueprint that serves as a tool-agnostic foundation for AI activity:
    • AI User Profile (ai_user)
  2. Extend the existing Cursor Daily Usage (cursorDailyUsage), User, Team, and Organization blueprints with the calculation and aggregation properties (and relations) needed to surface Cursor adoption metrics at user, team, and organization scope.

The data model touches the following blueprints and relations. Use this as a reference whenever an identifier appears later in the guide:

Title (identifier)TypeRole
Cursor Daily Usage (cursorDailyUsage)Blueprint (integration-owned)Daily per-user Cursor activity records
AI User Profile (ai_user)Blueprint (created here)Per-tool AI activity profile, one per user per tool
User (_user)Blueprint (foundation)Port users; surfaces each person's Cursor metrics
Team (_team)Blueprint (foundation)Port teams; rolls up member and child-team metrics
Organization (organization)Blueprint (foundation)The organization; holds org-wide KPIs
AI Tools (ai_tools)Relation on User โ†’ AI User ProfileLinks each Port user to their AI profile(s); every user/team/org rollup traverses it
AI User Profile (cursor_user)Relation on Cursor Daily Usage โ†’ AI User ProfileWires each daily user record to its profile. Cursor-specific; other AI tools add their own <tool>_user relation

Create the AI User Profile blueprintโ€‹

The ai_user blueprint stores the per-tool AI activity profile for each person. One entity per user per tool. It is the bridge between the raw per-day cursorDailyUsage records and each person's Port User profile.

Already have an AI User Profile blueprint?

ai_user is a shared, tool-agnostic blueprint. If it already exists (for example because you set it up for another AI tool), do not recreate it from the JSON below, as that would overwrite the other tool's configuration. Instead, open the existing blueprint, choose Edit JSON, and merge the Cursor-specific additions: add "Cursor" to the tool enum (keep any existing values), add the cursor_username property, then continue with the aggregation and calculation merges in steps 4 and 5. Skip step 3 below.

  1. Go to your Builder page.

  2. Click + Blueprint and choose Create from JSON.

  3. Paste the following JSON and click Create:

    AI User Profile blueprint (click to expand)
    {
    "identifier": "ai_user",
    "title": "AI User Profile",
    "icon": "User",
    "description": "Per-tool AI activity profile for a person. One entity per tool (Cursor, Copilot, โ€ฆ).",
    "schema": {
    "properties": {
    "display_name": {"icon": "User", "title": "Display Name", "type": "string"},
    "email": {"format": "user", "icon": "User", "title": "Email", "type": "string"},
    "cursor_username": {"title": "Cursor Username", "icon": "DefaultProperty", "type": "string"},
    "tool": {
    "title": "AI Tool",
    "icon": "DefaultProperty",
    "type": "string",
    "enum": ["Cursor"],
    "enumColors": {"Cursor": "purple"}
    }
    },
    "required": []
    },
    "mirrorProperties": {},
    "calculationProperties": {},
    "aggregationProperties": {},
    "relations": {}
    }
    Multiple AI tools

    The tool enum only includes Cursor for this guide. If you later layer GitHub Copilot, Claude Code, etc. on top of the same foundation, add their identifiers to this enum.

  4. Once created, open the blueprint, click the {...} button in the top right corner, choose Edit JSON, and merge the following entries into its aggregationProperties section. These roll the daily cursorDailyUsage records up to a monthly per-person profile:

    AI User Profile aggregation properties (click to expand)
    "cursor_loc_added_monthly": {
    "title": "Cursor LOC Added (Monthly)",
    "type": "number",
    "description": "Total lines of code accepted from Cursor suggestions this month",
    "target": "cursorDailyUsage",
    "query": {
    "combinator": "and",
    "rules": [
    {"operator": "between", "property": "day", "value": {"preset": "lastMonth"}}
    ]
    },
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "accepted_lines_added"}
    },
    "cursor_loc_suggested_monthly": {
    "title": "Cursor LOC Suggested (Monthly)",
    "type": "number",
    "description": "Total lines of code suggested by Cursor this month",
    "target": "cursorDailyUsage",
    "query": {
    "combinator": "and",
    "rules": [
    {"operator": "between", "property": "day", "value": {"preset": "lastMonth"}}
    ]
    },
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "total_lines_added"}
    },
    "cursor_loc_deleted_monthly": {
    "title": "Cursor LOC Deleted (Monthly)",
    "type": "number",
    "target": "cursorDailyUsage",
    "query": {
    "combinator": "and",
    "rules": [
    {"operator": "between", "property": "day", "value": {"preset": "lastMonth"}}
    ]
    },
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "total_lines_deleted"}
    },
    "cursor_days_active_monthly": {
    "title": "Cursor Days Active (Monthly)",
    "type": "number",
    "description": "Count of days the user was active in Cursor this month",
    "target": "cursorDailyUsage",
    "query": {
    "combinator": "and",
    "rules": [
    {"operator": "=", "property": "is_active", "value": true},
    {"operator": "between", "property": "day", "value": {"preset": "lastMonth"}}
    ]
    },
    "calculationSpec": {"calculationBy": "entities", "func": "count"}
    },
    "cursor_accepts_monthly": {
    "title": "Cursor Accepts (Monthly)",
    "type": "number",
    "target": "cursorDailyUsage",
    "query": {
    "combinator": "and",
    "rules": [
    {"operator": "between", "property": "day", "value": {"preset": "lastMonth"}}
    ]
    },
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "total_accepts"}
    },
    "cursor_applies_monthly": {
    "title": "Cursor Applies (Monthly)",
    "type": "number",
    "target": "cursorDailyUsage",
    "query": {
    "combinator": "and",
    "rules": [
    {"operator": "between", "property": "day", "value": {"preset": "lastMonth"}}
    ]
    },
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "total_applies"}
    },
    "cursor_tabs_shown_monthly": {
    "title": "Cursor Tabs Shown (Monthly)",
    "type": "number",
    "target": "cursorDailyUsage",
    "query": {
    "combinator": "and",
    "rules": [
    {"operator": "between", "property": "day", "value": {"preset": "lastMonth"}}
    ]
    },
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "total_tabs_shown"}
    },
    "cursor_tabs_accepted_monthly": {
    "title": "Cursor Tabs Accepted (Monthly)",
    "type": "number",
    "target": "cursorDailyUsage",
    "query": {
    "combinator": "and",
    "rules": [
    {"operator": "between", "property": "day", "value": {"preset": "lastMonth"}}
    ]
    },
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "total_tabs_accepted"}
    },
    "cursor_interactions_monthly": {
    "title": "Cursor Interactions (Monthly)",
    "type": "number",
    "description": "Sum of chat + composer + agent + cmd-k requests this month",
    "target": "cursorDailyUsage",
    "query": {
    "combinator": "and",
    "rules": [
    {"operator": "between", "property": "day", "value": {"preset": "lastMonth"}}
    ]
    },
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "total_requests"}
    },
    "cursor_days_using_agent_monthly": {
    "title": "Cursor Days Using Agent (Monthly)",
    "type": "number",
    "target": "cursorDailyUsage",
    "query": {
    "combinator": "and",
    "rules": [
    {"operator": ">", "property": "agent_requests", "value": 0},
    {"operator": "between", "property": "day", "value": {"preset": "lastMonth"}}
    ]
    },
    "calculationSpec": {"calculationBy": "entities", "func": "count"}
    },
    "cursor_days_using_chat_monthly": {
    "title": "Cursor Days Using Chat (Monthly)",
    "type": "number",
    "target": "cursorDailyUsage",
    "query": {
    "combinator": "and",
    "rules": [
    {"operator": ">", "property": "chat_requests", "value": 0},
    {"operator": "between", "property": "day", "value": {"preset": "lastMonth"}}
    ]
    },
    "calculationSpec": {"calculationBy": "entities", "func": "count"}
    }
  5. Merge the following entries into the existing calculationProperties section (keep any properties already there). Each is guarded by tool == "Cursor" so it stays null on profiles for other AI tools:

    AI User Profile calculation properties (click to expand)
    "cursor_loc_efficiency": {
    "title": "Cursor LOC Efficiency (%)",
    "icon": "Metric",
    "description": "Percentage of Cursor lines added that were accepted this month",
    "calculation": "if .properties.tool == \"Cursor\" then (if (.properties.cursor_loc_suggested_monthly // 0) > 0 then ((.properties.cursor_loc_added_monthly // 0) / .properties.cursor_loc_suggested_monthly * 100 | round) else 0 end) else null end",
    "type": "number"
    },
    "cursor_acceptance_rate": {
    "title": "Cursor Acceptance Rate (%)",
    "icon": "Metric",
    "description": "Percentage of Cursor applies accepted this month (accepts/applies)",
    "calculation": "if .properties.tool == \"Cursor\" then (if (.properties.cursor_applies_monthly // 0) > 0 then ((.properties.cursor_accepts_monthly // 0) / .properties.cursor_applies_monthly * 100 | round) else 0 end) else null end",
    "type": "number"
    },
    "cursor_tab_acceptance_rate": {
    "title": "Cursor Tab Acceptance Rate (%)",
    "icon": "Metric",
    "description": "Percentage of Cursor tab completions accepted this month",
    "calculation": "if .properties.tool == \"Cursor\" then (if (.properties.cursor_tabs_shown_monthly // 0) > 0 then ((.properties.cursor_tabs_accepted_monthly // 0) / .properties.cursor_tabs_shown_monthly * 100 | round) else 0 end) else null end",
    "type": "number"
    },
    "cursor_adoption_tier": {
    "title": "Cursor Adoption Tier",
    "icon": "Metric",
    "description": "Individual usage intensity based on days active last month: None (0) / Low (1-5) / Medium (6-15) / High (16+).",
    "calculation": "if .properties.tool != \"Cursor\" then null else ((.properties.cursor_days_active_monthly // 0) as $d | if $d == 0 then \"None\" elif $d <= 5 then \"Low\" elif $d <= 15 then \"Medium\" else \"High\" end) end",
    "type": "string",
    "colorized": true,
    "colors": {"High": "green", "Low": "yellow", "Medium": "blue", "None": "darkGray"}
    },
    "is_cursor_active_monthly": {
    "title": "Cursor Active (Monthly)",
    "icon": "DefaultProperty",
    "description": "True if this person used Cursor at least one day this month",
    "calculation": "if .properties.tool == \"Cursor\" then ((.properties.cursor_days_active_monthly // 0) > 0) else null end",
    "type": "boolean"
    },
    "is_cursor_high_adopter": {
    "title": "Cursor High Adopter",
    "icon": "DefaultProperty",
    "description": "True if this person used Cursor 16+ days this month",
    "calculation": "if .properties.tool == \"Cursor\" then ((.properties.cursor_days_active_monthly // 0) >= 16) else null end",
    "type": "boolean"
    },
    "is_cursor_inactive": {
    "title": "Cursor Inactive This Month",
    "icon": "DefaultProperty",
    "description": "True if this is a Cursor profile with zero active days this month",
    "calculation": "if .properties.tool == \"Cursor\" then ((.properties.cursor_days_active_monthly // 0) == 0) else null end",
    "type": "boolean"
    }
  6. Click Save to update the blueprint.

Update blueprintsโ€‹

Update the Cursor Daily Usage blueprint

cursorDailyUsage is owned by the Cursor integration. We extend it (without replacing it) with a relation that links each daily record to the right ai_user profile, plus a derived total_requests property. The relation is the important one: every ai_user monthly aggregation traverses it to find its records, so without it the entire Port-scoped rollup chain stays empty.

  1. Go to the Builder page of your portal.

  2. Find the Cursor Daily Usage blueprint and click on it.

  3. Click on the {...} button in the top right corner, choose Edit JSON, and merge the entries below into the existing definition - do not replace it.

  4. Merge the following entry into the existing schema.properties section. It is populated by the integration mapping (see Update integration mapping) and powers the Total Requests trend chart and the cursor_interactions_monthly rollup:

    Total requests property (click to expand)
    "total_requests": {
    "title": "Total Requests",
    "description": "Sum of chat + composer + agent + cmd-k requests that day.",
    "type": "number"
    }
  5. Merge the following entry into the existing relations section. It is the relation the integration mapping uses to wire each daily record to the matching AI User Profile, and the one every ai_user aggregation traverses:

    Cursor User relation (click to expand)
    "cursor_user": {
    "title": "AI User Profile",
    "target": "ai_user",
    "required": false,
    "many": false
    }
  6. Click Save to update the blueprint.

Update the User blueprint

Add a relation to AI User Profile, plus aggregation and calculation properties to the existing User blueprint so that each Port user surfaces their own Cursor activity, acceptance, efficiency, and adoption tier.

  1. Go to your Builder page.

  2. Find the User blueprint and click on it.

  3. Click on the {...} button in the top right corner, choose Edit JSON, and merge the entries below into the existing definition - do not replace it.

  4. Merge the following entry into the existing relations section. This is the link that all User-level aggregations below traverse, and it is also what you populate in the AI Tools field when registering a Port User:

    AI Tools relation (click to expand)
    "ai_tools": {
    "title": "AI Tools",
    "target": "ai_user",
    "required": false,
    "many": true
    }
  5. Merge the following entries into the existing aggregationProperties section (keep any properties already there). These roll up the per-tool Cursor metrics from the linked ai_user entity:

    User Cursor aggregation properties (click to expand)
    "cursor_loc_added_monthly": {
    "title": "Cursor LOC Added (Monthly)",
    "type": "number",
    "description": "Total lines accepted from Cursor suggestions this month",
    "target": "ai_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "tool", "value": "Cursor"}]},
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_loc_added_monthly"}
    },
    "cursor_loc_suggested_monthly": {
    "title": "Cursor LOC Suggested (Monthly)",
    "type": "number",
    "description": "Total lines suggested by Cursor this month",
    "target": "ai_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "tool", "value": "Cursor"}]},
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_loc_suggested_monthly"}
    },
    "cursor_loc_deleted_monthly": {
    "title": "Cursor LOC Deleted (Monthly)",
    "type": "number",
    "target": "ai_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "tool", "value": "Cursor"}]},
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_loc_deleted_monthly"}
    },
    "cursor_days_active_monthly": {
    "title": "Cursor Days Active (Monthly)",
    "type": "number",
    "target": "ai_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "tool", "value": "Cursor"}]},
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_days_active_monthly"}
    },
    "cursor_accepts_monthly": {
    "title": "Cursor Accepts (Monthly)",
    "type": "number",
    "target": "ai_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "tool", "value": "Cursor"}]},
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_accepts_monthly"}
    },
    "cursor_applies_monthly": {
    "title": "Cursor Applies (Monthly)",
    "type": "number",
    "target": "ai_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "tool", "value": "Cursor"}]},
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_applies_monthly"}
    },
    "cursor_interactions_monthly": {
    "title": "Cursor Interactions (Monthly)",
    "type": "number",
    "target": "ai_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "tool", "value": "Cursor"}]},
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_interactions_monthly"}
    },
    "cursor_days_using_agent_monthly": {
    "title": "Cursor Days Using Agent (Monthly)",
    "type": "number",
    "target": "ai_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "tool", "value": "Cursor"}]},
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_days_using_agent_monthly"}
    },
    "cursor_days_using_chat_monthly": {
    "title": "Cursor Days Using Chat (Monthly)",
    "type": "number",
    "target": "ai_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "tool", "value": "Cursor"}]},
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_days_using_chat_monthly"}
    }
  6. Merge the following entries into the existing calculationProperties section (keep any properties already there). These derive engagement flags and adoption tiers referenced by the dashboard widgets. primary_ai_tool is tool-agnostic: it compares Cursor against Copilot, and stays correct on a Cursor-only tenant (it reads copilot_days_active_monthly as 0 when that property does not exist):

    User Cursor calculation properties (click to expand)
    "cursor_loc_efficiency": {
    "title": "Cursor LOC Efficiency (%)",
    "icon": "Metric",
    "description": "Percentage of Cursor lines added that were accepted this month",
    "calculation": "if (.properties.cursor_loc_suggested_monthly // 0) > 0 then ((.properties.cursor_loc_added_monthly // 0) / .properties.cursor_loc_suggested_monthly * 100 | round) else 0 end",
    "type": "number"
    },
    "cursor_adoption_tier": {
    "title": "Cursor Adoption Tier",
    "icon": "Metric",
    "description": "Individual usage intensity based on days active last month: None (0) / Low (1-5) / Medium (6-15) / High (16+).",
    "calculation": "(.properties.cursor_days_active_monthly // 0) as $d | if $d == 0 then \"None\" elif $d <= 5 then \"Low\" elif $d <= 15 then \"Medium\" else \"High\" end",
    "type": "string",
    "colorized": true,
    "colors": {"High": "green", "Low": "yellow", "Medium": "blue", "None": "darkGray"}
    },
    "cursor_acceptance_rate": {
    "title": "Cursor Acceptance Rate (%)",
    "icon": "Metric",
    "description": "Percentage of Cursor applies accepted this month",
    "calculation": "if (.properties.cursor_applies_monthly // 0) > 0 then ((.properties.cursor_accepts_monthly // 0) / .properties.cursor_applies_monthly * 100 | round) else 0 end",
    "type": "number"
    },
    "is_cursor_active_monthly": {
    "title": "Cursor Active (Monthly)",
    "icon": "DefaultProperty",
    "description": "True if this person used Cursor at least one day this month",
    "calculation": "(.properties.cursor_days_active_monthly // 0) > 0",
    "type": "boolean"
    },
    "is_cursor_high_adopter": {
    "title": "Cursor High Adopter",
    "icon": "DefaultProperty",
    "description": "True if this person used Cursor 16+ days this month",
    "calculation": "(.properties.cursor_days_active_monthly // 0) >= 16",
    "type": "boolean"
    },
    "is_cursor_inactive": {
    "title": "Cursor Inactive This Month",
    "icon": "DefaultProperty",
    "description": "True if this person appears in Cursor data but had zero active days this month",
    "calculation": "(.properties.cursor_days_active_monthly != null) and ((.properties.cursor_days_active_monthly // 0) == 0)",
    "type": "boolean"
    },
    "primary_ai_tool": {
    "title": "Primary AI Tool",
    "icon": "Metric",
    "description": "Tool with the most active days this month (Both = tie, None = no AI activity)",
    "calculation": "(.properties.copilot_days_active_monthly // 0) as $c | (.properties.cursor_days_active_monthly // 0) as $cu | if ($c == 0 and $cu == 0) then \"None\" elif $c > $cu then \"Copilot\" elif $cu > $c then \"Cursor\" else \"Both\" end",
    "type": "string",
    "colorized": true,
    "colors": {"Copilot": "blue", "Cursor": "purple", "Both": "green", "None": "darkGray"}
    }
  7. Click Save to update the blueprint.

Update the Team blueprint

Add aggregation and calculation properties to the existing Team blueprint so that each team displays its own Cursor adoption metrics. Aggregations over the User blueprint automatically scope to a team's members.

  1. Go to your Builder page.

  2. Find the Team blueprint and click on it.

  3. Click on the {...} button in the top right corner, choose Edit JSON, and merge the entries below into the existing definition - do not replace it.

  4. Merge the following entries into the existing aggregationProperties section (keep any properties already there):

    Team Cursor aggregation properties (click to expand)
    "cursor_active_users_monthly": {
    "title": "Cursor Active Users (Monthly)",
    "type": "number",
    "description": "Number of team members who used Cursor at least one day this month",
    "target": "_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "is_cursor_active_monthly", "value": true}]},
    "calculationSpec": {"calculationBy": "entities", "func": "count"}
    },
    "cursor_inactive_users_monthly": {
    "title": "Cursor Inactive Users (Monthly)",
    "type": "number",
    "description": "Team members who appear in Cursor data but had zero active days this month",
    "target": "_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "is_cursor_inactive", "value": true}]},
    "calculationSpec": {"calculationBy": "entities", "func": "count"}
    },
    "cursor_high_adopters_monthly": {
    "title": "Cursor High Adopters (Monthly)",
    "type": "number",
    "description": "Team members who used Cursor 16+ days this month",
    "target": "_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "is_cursor_high_adopter", "value": true}]},
    "calculationSpec": {"calculationBy": "entities", "func": "count"}
    },
    "cursor_loc_added_monthly": {
    "title": "Cursor LOC Added (Monthly)",
    "type": "number",
    "description": "Total lines accepted from Cursor suggestions across team members this month",
    "target": "_user",
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_loc_added_monthly"}
    },
    "cursor_loc_suggested_monthly": {
    "title": "Cursor LOC Suggested (Monthly)",
    "type": "number",
    "description": "Total lines suggested by Cursor across team members this month",
    "target": "_user",
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_loc_suggested_monthly"}
    },
    "cursor_accepts_monthly": {
    "title": "Cursor Total Accepts (Monthly)",
    "type": "number",
    "target": "_user",
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_accepts_monthly"}
    },
    "cursor_applies_monthly": {
    "title": "Cursor Total Applies (Monthly)",
    "type": "number",
    "target": "_user",
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_applies_monthly"}
    },
    "cursor_interactions_monthly": {
    "title": "Cursor Interactions (Monthly)",
    "type": "number",
    "target": "_user",
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_interactions_monthly"}
    },
    "child_members_count": {
    "title": "Child Teams Member Count",
    "type": "number",
    "description": "Sum of direct members across direct child teams (one level down)",
    "target": "_team",
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "size"}
    }
  5. Merge the following entries into the existing calculationProperties section (keep any properties already there):

    Team Cursor calculation properties (click to expand)
    "total_members": {
    "title": "Total Members",
    "icon": "Metric",
    "description": "Direct members plus members from direct child teams (one level down)",
    "calculation": "(.properties.size // 0) + (.properties.child_members_count // 0)",
    "type": "number"
    },
    "cursor_adoption_rate": {
    "title": "Cursor Adoption Rate (%)",
    "icon": "Metric",
    "description": "Percentage of direct team members active on Cursor at least once this month",
    "calculation": "(.properties.cursor_active_users_monthly // 0) as $active | (.properties.total_members // 0) as $total | if $total == 0 then 0 else (($active / $total) * 100 | round) as $r | if $r > 100 then 100 else $r end end",
    "type": "number"
    },
    "cursor_acceptance_rate": {
    "title": "Cursor Acceptance Rate (%)",
    "icon": "Metric",
    "description": "Team-level Cursor apply acceptance rate this month",
    "calculation": "if (.properties.cursor_applies_monthly // 0) > 0 then ((.properties.cursor_accepts_monthly // 0) / .properties.cursor_applies_monthly) * 100 | round else 0 end",
    "type": "number"
    },
    "cursor_loc_efficiency": {
    "title": "Cursor LOC Efficiency (%)",
    "icon": "Metric",
    "description": "Percentage of Cursor-suggested lines accepted across team members this month",
    "calculation": "if (.properties.cursor_loc_suggested_monthly // 0) > 0 then ((.properties.cursor_loc_added_monthly // 0) / .properties.cursor_loc_suggested_monthly) * 100 | round else 0 end",
    "type": "number"
    },
    "cursor_inactive_license_rate": {
    "title": "Cursor Inactive Users (%)",
    "icon": "Metric",
    "description": "Percentage of Cursor users on this team with zero activity this month",
    "calculation": "((.properties.cursor_active_users_monthly // 0) + (.properties.cursor_inactive_users_monthly // 0)) as $total | if $total == 0 then 0 else ((.properties.cursor_inactive_users_monthly // 0) / $total) * 100 | round end",
    "type": "number"
    },
    "cursor_adoption_tier": {
    "title": "Cursor Adoption Tier",
    "icon": "Metric",
    "description": "Team coverage tier based on direct members: None (0%) / Low (1-33%) / Medium (34-66%) / High (67-100%).",
    "calculation": "(.properties.cursor_active_users_monthly // 0) as $active | (.properties.total_members // 0) as $total | if $total == 0 then \"None\" else (($active / $total) * 100 | round) as $r | if $r == 0 then \"None\" elif $r <= 33 then \"Low\" elif $r <= 66 then \"Medium\" else \"High\" end end",
    "type": "string",
    "colorized": true,
    "colors": {"High": "green", "Low": "yellow", "Medium": "blue", "None": "darkGray"}
    },
    "cursor_high_adopter_rate": {
    "title": "Cursor High Adoption Rate (%)",
    "icon": "Metric",
    "description": "Percentage of team members who used Cursor 16+ days this month",
    "calculation": "(.properties.total_members // 0) as $s | if $s == 0 then 0 else ((.properties.cursor_high_adopters_monthly // 0) / $s) * 100 | round end",
    "type": "number"
    }

    Note on total_members and size: The Team blueprint already has a built-in size aggregation (count of direct members) provided by Port's _team system blueprint. The total_members calculation above combines size with child_members_count so adoption rates work for parent teams as well as leaf teams.

  6. Click Save to update the blueprint.

Update the Organization blueprint

Add aggregation and calculation properties to the existing organization blueprint so that the org-wide Cursor KPIs (adoption rate, inactive license rate, etc.) used by the top-row dashboard tiles resolve correctly.

  1. Go to your Builder page.

  2. Find the Organization blueprint and click on it.

  3. Click on the {...} button in the top right corner, choose Edit JSON, and merge the entries below into the existing definition - do not replace it.

  4. Merge the following entries into the existing aggregationProperties section (keep any properties already there):

    Organization Cursor aggregation properties (click to expand)
    "total_port_users": {
    "title": "Total Port Users",
    "type": "number",
    "description": "Total number of active Standard users in Port. Used as denominator for org-level adoption rates.",
    "target": "_user",
    "query": {
    "combinator": "and",
    "rules": [
    {"operator": "=", "property": "status", "value": "Active"},
    {"operator": "=", "property": "port_type", "value": "Standard"}
    ]
    },
    "calculationSpec": {"calculationBy": "entities", "func": "count"}
    },
    "cursor_active_users_monthly": {
    "title": "Cursor Active Users (Monthly)",
    "type": "number",
    "description": "Distinct users active on Cursor at least one day this month",
    "target": "_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "is_cursor_active_monthly", "value": true}]},
    "calculationSpec": {"calculationBy": "entities", "func": "count"}
    },
    "cursor_inactive_users_monthly": {
    "title": "Cursor Inactive Users (Monthly)",
    "type": "number",
    "description": "Distinct users who appear in Cursor data but had zero active days this month",
    "target": "_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "is_cursor_inactive", "value": true}]},
    "calculationSpec": {"calculationBy": "entities", "func": "count"}
    },
    "cursor_high_adopters_monthly": {
    "title": "Cursor High Adopters (Monthly)",
    "type": "number",
    "description": "Distinct users who used Cursor 16+ days this month",
    "target": "_user",
    "query": {"combinator": "and", "rules": [{"operator": "=", "property": "is_cursor_high_adopter", "value": true}]},
    "calculationSpec": {"calculationBy": "entities", "func": "count"}
    },
    "cursor_loc_added_monthly": {
    "title": "Cursor LOC Added (Monthly)",
    "type": "number",
    "description": "Total lines accepted from Cursor suggestions across all users this month",
    "target": "_user",
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_loc_added_monthly"}
    },
    "cursor_loc_suggested_monthly": {
    "title": "Cursor LOC Suggested (Monthly)",
    "type": "number",
    "description": "Total lines suggested by Cursor across all users this month",
    "target": "_user",
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_loc_suggested_monthly"}
    },
    "cursor_accepts_monthly": {
    "title": "Cursor Total Accepts (Monthly)",
    "type": "number",
    "target": "_user",
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_accepts_monthly"}
    },
    "cursor_applies_monthly": {
    "title": "Cursor Total Applies (Monthly)",
    "type": "number",
    "target": "_user",
    "calculationSpec": {"calculationBy": "property", "func": "sum", "property": "cursor_applies_monthly"}
    }
  5. Merge the following entries into the existing calculationProperties section (keep any properties already there):

    Organization Cursor calculation properties (click to expand)
    "cursor_adoption_rate": {
    "title": "Cursor Adoption Rate (%)",
    "icon": "Metric",
    "description": "Percentage of Port users active on Cursor this month",
    "calculation": "(.properties.total_port_users // 0) as $t | if $t == 0 then 0 else (((.properties.cursor_active_users_monthly // 0) / $t) * 100 | round) as $r | if $r > 100 then 100 else $r end end",
    "type": "number"
    },
    "cursor_acceptance_rate": {
    "title": "Cursor Acceptance Rate (%)",
    "icon": "Metric",
    "description": "Org-level Cursor apply acceptance rate this month",
    "calculation": "if (.properties.cursor_applies_monthly // 0) > 0 then ((.properties.cursor_accepts_monthly // 0) / .properties.cursor_applies_monthly) * 100 | round else 0 end",
    "type": "number"
    },
    "cursor_loc_efficiency": {
    "title": "Cursor LOC Efficiency (%)",
    "icon": "Metric",
    "description": "Percentage of Cursor-suggested lines accepted across the entire org this month",
    "calculation": "if (.properties.cursor_loc_suggested_monthly // 0) > 0 then ((.properties.cursor_loc_added_monthly // 0) / .properties.cursor_loc_suggested_monthly) * 100 | round else 0 end",
    "type": "number"
    },
    "cursor_inactive_license_rate": {
    "title": "Cursor Inactive Users (%)",
    "icon": "Metric",
    "description": "Percentage of Cursor users with zero activity this month",
    "calculation": "((.properties.cursor_active_users_monthly // 0) + (.properties.cursor_inactive_users_monthly // 0)) as $total | if $total == 0 then 0 else ((.properties.cursor_inactive_users_monthly // 0) / $total) * 100 | round end",
    "type": "number"
    },
    "cursor_high_adopter_rate": {
    "title": "Cursor High Adoption Rate (%)",
    "icon": "Metric",
    "description": "Percentage of Port users who reached Cursor High adoption tier this month",
    "calculation": "(.properties.total_port_users // 0) as $t | if $t == 0 then 0 else ((.properties.cursor_high_adopters_monthly // 0) / $t) * 100 | round end",
    "type": "number"
    }
  6. Click Save to update the blueprint.

Update integration mappingโ€‹

The native Cursor integration already ingests the daily per-user records into the Cursor Daily Usage (cursorDailyUsage) blueprint. To power the metrics in this guide, it needs two additions:

  • A cursor_user relation and a derived total_requests property on the existing cursor-daily-usage resource, so each daily record links to its profile and exposes a single requests total, and
  • A new resource entry that creates entities in the AI User Profile (ai_user) blueprint - one per Cursor user.

Follow the steps below to update the integration mapping:

  1. Go to your Data Sources page.

  2. Click on the Cursor integration.

  3. Open the Mapping tab. Your existing resources already map the daily-record properties - leave those untouched, and keep the other resources (cursor-usage-event, cursor-team-model-usage, cursor-user-model-usage) as they are. Make only the following additions:

    • Extend the cursor-daily-usage resource with the total_requests property and the cursor_user relation. The additions are marked # add this:

      Cursor Daily Usage additions (click to expand)
      - kind: cursor-daily-usage
      selector:
      query: 'true'
      startDate: 30d
      endDate: 0d
      port:
      entity:
      mappings:
      blueprint: '"cursorDailyUsage"'
      identifier: (.userId + "-" + .day)
      title: (.email + " " + .day)
      properties:
      # ... keep all the properties the integration already maps ...
      total_requests: ((.composerRequests // 0) + (.chatRequests // 0) + (.agentRequests // 0) + (.cmdkUsages // 0)) # add this
      relations: # add this
      cursor_user: .email # add this
    • Add a new resource entry that creates the AI User Profile (ai_user) entities, one per Cursor user (keyed by email so it matches your Port User identifier):

      AI User Profile resource (click to expand)
      - kind: cursor-daily-usage
      selector:
      query: '.email != null and .email != ""'
      startDate: 30d
      endDate: 0d
      port:
      entity:
      mappings:
      blueprint: '"ai_user"'
      identifier: .email
      title: (.email + " (Cursor)")
      properties:
      display_name: .email
      email: .email
      cursor_username: .email
      tool: '"Cursor"'
  4. Click Save & Resync to apply the mapping and trigger a sync.

After the resync completes, you should see one ai_user entity per Cursor user (titled e.g. jane@acme.io (Cursor)) in your software catalog.

The AI Tools relation (ai_tools) you added to the User blueprint earlier in this guide points to one or many ai_user entities. All the aggregations defined here at the user, team, and organization levels traverse this relation: a Port user with no ai_tools link will show zero Cursor activity even if a matching ai_user entity exists.

Once the integration has created the ai_user entities, link each Port User to their matching Cursor profile. For a small number of users, you can assign them manually in the UI:

  1. Go to your software catalog and open the User entity for the person you want to link (or create a new User entity if needed).
  2. In the AI Tools field, select the matching ai_user profile (e.g. jane@acme.io (Cursor)).
  3. Save the entity.
Linking a Port User entity to their matching AI User Profile via the AI Tools field

Automate this step

For larger orgs, doing this one-by-one in the UI is slow. You can populate the ai_tools relation automatically in several ways:

  • Port AI: ask Port AI to link every Port User to their matching ai_user profile by email. It is the fastest option for one-off bulk runs and requires no code.
  • Integration mapping: extend the User-creation mapping in your SCM integration (e.g. GitHub, GitLab, Azure DevOps) so that whenever a User entity is upserted, ai_tools is set via a query lookup against ai_user (match on email). See the Engineering Intelligence data model guide for examples of relation lookups in mappings. This keeps the link in sync as new users join.
  • Automation: create a Port automation that triggers when an ai_user entity is created and upserts the matching Port User (by email), setting ai_tools to that profile. This links users reactively as their ai_user profiles appear. As with the mapping option, make sure the upsert preserves any existing ai_tools values if you run more than one AI tool.
  • Scripts / API: run a one-shot script against Port's REST API to bulk-patch User entities. Useful for backfills or when matching logic is more complex than what a mapping can express.
Metrics take up to ~15 minutes to appear

Aggregation and calculation property values are recalculated for all entities of a blueprint on a periodic cycle (roughly every 15 minutes, see aggregation property limitations). After you create these properties, resync the integration, or link a Port user to their ai_user profile, the metrics at the user, team, and organization levels may show 0 or empty for up to ~15 minutes even when everything is configured correctly. This is expected, so wait for the next recalculation before troubleshooting an empty dashboard. If values are still 0 after the recalculation window, then check the ai_tools link and the underlying ai_user data as described above.

High adopter tier needs history

The High tier and cursor_high_adopter_rate require 16+ active days in the rolling last month, so they only become reachable once your tenant has ingested roughly 16 days of Cursor history. A 0% high-adopter rate on a freshly-connected tenant is expected, not a bug.

Visualize metricsโ€‹

We will create a dedicated dashboard to monitor Cursor adoption using Port's customizable widgets. The dashboard is organized into sections covering headline KPIs, distribution by adoption tier, team-level adoption, top contributors, low engagement, inactive users, and daily activity trends.

Create the dashboardโ€‹

First, let's create an Engineering Intelligence folder to organize your dashboards (if you have not already), then add the Cursor AI Adoption dashboard inside it:

  1. Navigate to your software catalog.
  2. Click on the + New button in the left sidebar.
  3. Select New folder.
  4. Name the folder Engineering Intelligence and click Create.
  5. Inside the Engineering Intelligence folder, click + New again.
  6. Select New dashboard.
  7. Name the dashboard Cursor AI Adoption and click Create.

Add widgetsโ€‹

You can populate the dashboard using either an API script or by manually creating each widget through the UI.

The fastest way to set up the dashboard is by using Port's API to create all widgets at once.

Get your Port API token

  1. In your Port portal, click on your profile picture in the top right corner.

  2. Select Credentials.

  3. Click Generate API token.

  4. Copy the generated token and store it as an environment variable:

    export PORT_ACCESS_TOKEN="YOUR_GENERATED_TOKEN"
EU region

If your portal is hosted in the EU region, replace api.port.io with api.port-eu.io in the dashboard creation command below.

Create the dashboard with widgets

Save the following JSON to a file named cursor_ai_adoption_dashboard.json:

Dashboard JSON payload (click to expand)
{
"identifier": "cursor-ai-adoption",
"title": "Cursor AI Adoption",
"icon": "Cursor",
"description": "Cursor AI adoption based on Port users, unless otherwise stated.",
"type": "dashboard",
"parent": "engineering_intelligence",
"widgets": [
{
"id": "dashboard-widget-root",
"type": "dashboard-widget",
"layout": [
{"height": 400, "columns": [
{"id": "org_adoption_rate", "size": 4},
{"id": "active_users", "size": 4},
{"id": "users_by_adoption_tier_pie", "size": 4}
]},
{"height": 400, "columns": [
{"id": "inactive_licenses", "size": 4},
{"id": "inactive_users_count", "size": 4},
{"id": "high_adopter_rate", "size": 4}
]},
{"height": 600, "columns": [{"id": "team_adoption_table", "size": 12}]},
{"height": 400, "columns": [{"id": "top_contributors_by_loc", "size": 12}]},
{"height": 400, "columns": [{"id": "low_engagement", "size": 12}]},
{"height": 400, "columns": [{"id": "inactive_cursor_users", "size": 12}]},
{"height": 400, "columns": [
{"id": "cursor_daily_activity", "size": 6},
{"id": "cursor_chat_agent_usage", "size": 6}
]},
{"height": 400, "columns": [
{"id": "cursor_loc_trends", "size": 6},
{"id": "cursor_acceptance_trend", "size": 6}
]},
{"height": 500, "columns": [{"id": "ai_user_cursor_table", "size": 12}]}
],
"widgets": [
{
"id": "org_adoption_rate",
"type": "entities-number-chart",
"title": "Adoption Rate",
"icon": "TrendingUp",
"description": "% of Port users actively using Cursor this month",
"blueprint": "organization",
"chartType": "displaySingleProperty",
"entity": "default-org",
"property": "cursor_adoption_rate",
"unit": "%",
"unitAlignment": "right"
},
{
"id": "active_users",
"type": "entities-number-chart",
"title": "Active Users",
"icon": "Users",
"description": "Port users who used Cursor at least once this month",
"blueprint": "organization",
"chartType": "displaySingleProperty",
"entity": "default-org",
"property": "cursor_active_users_monthly",
"unit": "custom",
"unitCustom": "users"
},
{
"id": "users_by_adoption_tier_pie",
"type": "entities-pie-chart",
"title": "Users by Adoption Tier",
"icon": "PieChart",
"description": "Intensity distribution for Port users using Cursor",
"blueprint": "_user",
"property": "calculation-property#cursor_adoption_tier",
"dataset": {
"combinator": "and",
"rules": [
{"property": "is_cursor_active_monthly", "operator": "=", "value": true}
]
}
},
{
"id": "inactive_licenses",
"type": "entities-number-chart",
"title": "Inactive Users",
"icon": "AlertCircle",
"description": "% of Port users using Cursor with zero activity this month",
"blueprint": "organization",
"chartType": "displaySingleProperty",
"entity": "default-org",
"property": "cursor_inactive_license_rate",
"unit": "%",
"unitAlignment": "right"
},
{
"id": "inactive_users_count",
"type": "entities-number-chart",
"title": "Inactive Users (this month)",
"icon": "Users",
"description": "Port users with zero Cursor activity this month",
"blueprint": "organization",
"chartType": "displaySingleProperty",
"entity": "default-org",
"property": "cursor_inactive_users_monthly",
"unit": "custom",
"unitCustom": "users"
},
{
"id": "high_adopter_rate",
"type": "entities-number-chart",
"title": "High Adopter Rate",
"icon": "Award",
"description": "% of Port users using Cursor 16+ days this month (power-user threshold)",
"blueprint": "organization",
"chartType": "displaySingleProperty",
"entity": "default-org",
"property": "cursor_high_adopter_rate",
"unit": "%",
"unitAlignment": "right"
},
{
"id": "team_adoption_table",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Cursor adoption per team",
"icon": "TrendingUp",
"description": "Teams ranked by share of members using Cursor at least once last month",
"blueprint": "_team",
"dataset": {
"combinator": "and",
"rules": [{"property": "type", "operator": "=", "value": "team"}]
},
"excludedFields": [],
"blueprintConfig": {
"_team": {
"filterSettings": {"filterBy": {"combinator": "and", "rules": []}},
"groupSettings": {"groupBy": []},
"propertiesSettings": {
"order": ["$title", "cursor_adoption_tier", "cursor_adoption_rate", "cursor_acceptance_rate", "cursor_loc_efficiency", "cursor_loc_added_monthly", "total_members", "cursor_active_users_monthly", "cursor_high_adopters_monthly", "cursor_interactions_monthly"],
"shown": ["$title", "cursor_adoption_tier", "cursor_adoption_rate", "cursor_acceptance_rate", "cursor_loc_efficiency", "cursor_loc_added_monthly", "total_members", "cursor_active_users_monthly", "cursor_high_adopters_monthly", "cursor_interactions_monthly"]
},
"sortSettings": {"sortBy": [{"property": "cursor_adoption_rate", "order": "desc"}]}
}
}
},
{
"id": "top_contributors_by_loc",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Cursor User Contributors",
"icon": "Award",
"description": "Port users using Cursor who added AI-generated lines of code this month, ranked by LOC added",
"blueprint": "_user",
"dataset": {
"combinator": "and",
"rules": [
{"property": "is_cursor_active_monthly", "operator": "=", "value": true},
{"property": "cursor_loc_added_monthly", "operator": ">", "value": 0}
]
},
"excludedFields": [],
"blueprintConfig": {
"_user": {
"propertiesSettings": {
"shown": ["$identifier", "cursor_acceptance_rate", "cursor_loc_efficiency", "cursor_loc_added_monthly", "cursor_loc_suggested_monthly", "$team"]
},
"sortSettings": {"sortBy": [{"property": "cursor_loc_added_monthly", "order": "desc"}]}
}
}
},
{
"id": "low_engagement",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Low Engagement (โ‰ค5 Days)",
"icon": "AlertTriangle",
"description": "Port users using Cursor with 5 or fewer days of usage this month",
"blueprint": "_user",
"dataset": {
"combinator": "and",
"rules": [
{"property": "is_cursor_active_monthly", "operator": "=", "value": true},
{"property": "cursor_days_active_monthly", "operator": "<=", "value": 5}
]
},
"excludedFields": [],
"blueprintConfig": {
"_user": {
"propertiesSettings": {
"shown": ["$identifier", "cursor_adoption_tier", "cursor_acceptance_rate", "cursor_days_active_monthly", "$team"]
},
"sortSettings": {"sortBy": [{"property": "cursor_days_active_monthly", "order": "asc"}]}
}
}
},
{
"id": "inactive_cursor_users",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Port Users using Cursor with no output",
"icon": "EyeOff",
"description": "Port users using Cursor with zero lines of code added this month, or users marked Cursor-inactive",
"blueprint": "_user",
"dataset": {
"combinator": "or",
"rules": [
{"property": "is_cursor_inactive", "operator": "=", "value": true},
{
"combinator": "and",
"rules": [
{"property": "is_cursor_active_monthly", "operator": "=", "value": true},
{"property": "cursor_loc_added_monthly", "operator": "=", "value": 0}
]
}
]
},
"excludedFields": [],
"blueprintConfig": {
"_user": {
"propertiesSettings": {
"shown": ["$identifier", "$title", "cursor_acceptance_rate", "cursor_days_active_monthly", "cursor_loc_added_monthly", "$team"]
},
"sortSettings": {"sortBy": [{"property": "cursor_days_active_monthly", "order": "asc"}]}
}
}
},
{
"id": "cursor_daily_activity",
"type": "multi-line-chart",
"title": "Cursor Activity Trends",
"icon": "Activity",
"description": "Daily total requests vs accepted applies. Includes all Cursor users, not just those mapped in Port.",
"lines": [
{
"title": "Total Requests",
"blueprint": "cursorDailyUsage",
"chartType": "aggregatePropertiesValues",
"func": "sum",
"measureTimeBy": "day",
"properties": ["properties.total_requests"],
"dataset": {"combinator": "and", "rules": []}
},
{
"title": "Accepted Applies",
"blueprint": "cursorDailyUsage",
"chartType": "aggregatePropertiesValues",
"func": "sum",
"measureTimeBy": "day",
"properties": ["properties.total_accepts"],
"dataset": {"combinator": "and", "rules": []}
}
],
"nullValueDisplayMode": "null",
"timeInterval": "day",
"timeRange": {"preset": "last3Months"},
"xAxisTitle": "Date",
"yAxisTitle": "Count"
},
{
"id": "cursor_chat_agent_usage",
"type": "multi-line-chart",
"title": "Chat & Agent Adoption",
"icon": "MessageSquare",
"description": "Chat, agent and composer feature adoption. Includes all Cursor users, not just those mapped in Port.",
"lines": [
{
"title": "Agent Days",
"blueprint": "cursorDailyUsage",
"chartType": "countEntities",
"func": "count",
"measureTimeBy": "day",
"dataset": {"combinator": "and", "rules": [{"property": "agent_requests", "operator": ">", "value": 0}]}
},
{
"title": "Chat Days",
"blueprint": "cursorDailyUsage",
"chartType": "countEntities",
"func": "count",
"measureTimeBy": "day",
"dataset": {"combinator": "and", "rules": [{"property": "chat_requests", "operator": ">", "value": 0}]}
},
{
"title": "Composer Days",
"blueprint": "cursorDailyUsage",
"chartType": "countEntities",
"func": "count",
"measureTimeBy": "day",
"dataset": {"combinator": "and", "rules": [{"property": "composer_requests", "operator": ">", "value": 0}]}
}
],
"nullValueDisplayMode": "null",
"timeInterval": "day",
"timeRange": {"preset": "last3Months"},
"xAxisTitle": "Date",
"yAxisTitle": "Days"
},
{
"id": "cursor_loc_trends",
"type": "multi-line-chart",
"title": "LOC Trends",
"icon": "Code",
"description": "Lines suggested vs accepted. Includes all Cursor users, not just those mapped in Port.",
"lines": [
{
"title": "Lines Suggested",
"blueprint": "cursorDailyUsage",
"chartType": "aggregatePropertiesValues",
"func": "sum",
"measureTimeBy": "day",
"properties": ["properties.total_lines_added"],
"dataset": {"combinator": "and", "rules": []}
},
{
"title": "Lines Accepted",
"blueprint": "cursorDailyUsage",
"chartType": "aggregatePropertiesValues",
"func": "sum",
"measureTimeBy": "day",
"properties": ["properties.accepted_lines_added"],
"dataset": {"combinator": "and", "rules": []}
}
],
"nullValueDisplayMode": "null",
"timeInterval": "day",
"timeRange": {"preset": "last3Months"},
"xAxisTitle": "Date",
"yAxisTitle": "Lines"
},
{
"id": "cursor_acceptance_trend",
"type": "multi-line-chart",
"title": "Acceptance Volume Trend",
"icon": "Percent",
"description": "Daily totals of applies vs accepts. True acceptance rate for a day = Accepts รท Applies. Includes all Cursor users, not just those mapped in Port.",
"lines": [
{
"title": "Applies",
"blueprint": "cursorDailyUsage",
"chartType": "aggregatePropertiesValues",
"func": "sum",
"measureTimeBy": "day",
"properties": ["properties.total_applies"],
"dataset": {"combinator": "and", "rules": []}
},
{
"title": "Accepts",
"blueprint": "cursorDailyUsage",
"chartType": "aggregatePropertiesValues",
"func": "sum",
"measureTimeBy": "day",
"properties": ["properties.total_accepts"],
"dataset": {"combinator": "and", "rules": []}
}
],
"nullValueDisplayMode": "null",
"timeInterval": "day",
"timeRange": {"preset": "last3Months"},
"xAxisTitle": "Date",
"yAxisTitle": "Count"
},
{
"id": "ai_user_cursor_table",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Cursor User Profiles",
"icon": "User",
"description": "All Cursor users (AI User Profile entities) with usage metrics for last month, sorted by LOC added.",
"blueprint": "ai_user",
"dataset": {
"combinator": "and",
"rules": [{"property": "tool", "operator": "=", "value": "Cursor"}]
},
"excludedFields": [],
"blueprintConfig": {
"ai_user": {
"filterSettings": {"filterBy": {"combinator": "and", "rules": []}},
"groupSettings": {"groupBy": []},
"propertiesSettings": {
"order": ["$title", "cursor_adoption_tier", "cursor_days_active_monthly", "cursor_acceptance_rate", "cursor_loc_efficiency", "cursor_loc_added_monthly", "cursor_loc_suggested_monthly", "cursor_loc_deleted_monthly", "cursor_interactions_monthly", "cursor_days_using_agent_monthly", "cursor_days_using_chat_monthly"],
"shown": ["$title", "cursor_adoption_tier", "cursor_days_active_monthly", "cursor_acceptance_rate", "cursor_loc_efficiency", "cursor_loc_added_monthly", "cursor_loc_suggested_monthly", "cursor_loc_deleted_monthly", "cursor_interactions_monthly", "cursor_days_using_agent_monthly", "cursor_days_using_chat_monthly"]
},
"sortSettings": {"sortBy": [{"property": "cursor_loc_added_monthly", "order": "desc"}]}
}
}
}
]
}
]
}

Then run the following command to create the dashboard with all widgets:

curl -s -X POST "https://api.port.io/v1/pages" \
-H "Authorization: Bearer $PORT_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d @cursor_ai_adoption_dashboard.json | python3 -m json.tool
Engineering Intelligence folder

The script assumes an engineering_intelligence folder already exists in your catalog. If you haven't created it yet, follow steps 1-4 in the create the dashboard section first, otherwise you will run into an error when you run the script.

Extending to other AI toolsโ€‹

The data model in this guide is deliberately structured so that Cursor is just one tool plugged into a tool-agnostic foundation. You can layer GitHub Copilot, Claude Code, or any other AI coding assistant on top of the same backbone by following the same pattern.

The patternโ€‹

Each AI tool follows the same structure:

  1. Raw daily activity lives in a tool-specific blueprint (e.g. cursorDailyUsage for Cursor, githubCopilotUserUsage for Copilot). Each record is one user-day of activity.
  2. Per-tool user profile rolls daily records up to the month on the AI User Profile (ai_user) blueprint. The tool enum (Cursor, Copilot, etc.) discriminates one tool from another, and each user has one ai_user entity per tool they use.
  3. Cross-tool synthesis lives on the User, Team, and Organization blueprints. Aggregations sum across all linked ai_user entities filtered by tool, so adding a tool is purely additive. Existing Cursor metrics keep working. The primary_ai_tool calculation on the User blueprint already compares Cursor against Copilot and starts reporting Both once you add a second tool.

For the GitHub Copilot version of this guide, see Visualize GitHub Copilot metrics.

FAQโ€‹

1. Why is a team's adoption rate low even though its members use Cursor?

Adoption rate measures penetration: active Cursor users divided by all members (the team's or org's full headcount), not just those who hold a Cursor seat. A team where 1 of 11 members uses Cursor is genuinely at ~9% adoption, which is the signal you want when deciding whether to expand seats.

A low rate can also mean incomplete data rather than low usage. Every rollup counts a member only if they are linked to their Cursor profile via the ai_tools relation; unlinked members sit in the denominator but can never appear in the numerator. Before reading too much into a low number, make sure all members are linked (see Link Port users to their AI User profiles).

2. Should adoption rate count only users who already have a Cursor seat?

No, and this is deliberate. Restricting the denominator to seat holders turns adoption rate into a different metric and makes it degenerate at small numbers (1 assigned / 1 active = 100%, indistinguishable from 50/50), so you lose the ability to rank teams by scale of rollout. The whole-headcount denominator answers "how widely has Cursor spread?" and keeps teams comparable.

3. The dashboard shows 0 or empty right after setup. Is something broken?

Aggregation and calculation properties recalculate on a periodic cycle (roughly every 15 minutes), so values can lag for a short while after you resync the integration or link a user. Wait for the next recalculation before troubleshooting an empty dashboard.

Next stepsโ€‹

Once your dashboard is in place, consider these additional improvements:

  • Set up scorecards to grade teams against Cursor adoption targets (e.g. โ‰ฅ60% active users, โ‰ฅ40% acceptance rate).
  • Create automations to send Slack notifications when a team's adoption rate drops below threshold, or to flag Cursor users who have been inactive for two consecutive months.
  • Chart model and cost data using the cursorUsageEvent, cursorTeamModelUsage, and cursorUserModelUsage blueprints the integration also ships, to see which models are used most and what they cost. See Track AI coding assistants costs.
  • Correlate adoption with engineering outcomes by overlaying Cursor adoption against PR delivery metrics, DORA metrics, or pipeline reliability to see whether higher AI usage correlates with shorter cycle times, higher deployment frequency, or fewer failures.