Capabilities
This page describes the data ingestion capabilities of Port's GitLab integration.
Enrich entities with file contents
You can enrich project, folder, and file entities with the contents of specific files from your repositories. This is useful for including README files, CODEOWNERS, or other documentation directly in your entity properties.
To do this, use the includedFiles selector to specify which files to fetch, and then reference them in your mapping using .__includedFiles["<file_path>"].
resources:
- kind: project
selector:
query: 'true'
includedFiles:
- README.md
- CODEOWNERS
port:
entity:
mappings:
identifier: .path_with_namespace | gsub(" "; "")
title: .name
blueprint: '"service"'
properties:
readme: .__includedFiles["README.md"]
codeowners: .__includedFiles["CODEOWNERS"]
url: .web_url
The file:// prefix in mappings is deprecated and will be removed in a future version. Use the includedFiles selector instead.
The file:// prefix will continue to work but will show deprecation warnings in the logs.
The includedFiles feature:
- Fetches file contents from the project's default branch (or specified branch for folder/file entities).
- Stores file contents in the
__includedFilesobject in the raw data. - Handles missing files gracefully (stores
nullif a file doesn't exist). - Works with the
projectkind (and with folder/file kinds for their respective flows). Theproject-with-memberskind does not supportincludedFiles.
Enrich entities with search queries
You can enrich project entities with search query results to check for the existence of files, patterns, or content in your repositories. This is useful for compliance checks, policy enforcement, and scorecard evaluations.
To do this, use the searchQueries selector to specify which search queries to execute, and then reference the results in your mapping using .__searchQueries["<name>"].
resources:
- kind: project
selector:
query: 'true'
searchQueries:
- name: hasPortConfig
scope: blobs
query: 'filename:port.yml'
- name: hasCI
scope: blobs
query: 'filename:.gitlab-ci.yml'
- name: hasDockerfile
scope: blobs
query: 'filename:Dockerfile'
port:
entity:
mappings:
identifier: .path_with_namespace | gsub(" "; "")
title: .name
blueprint: '"service"'
properties:
hasPortConfig: .__searchQueries["hasPortConfig"]
hasCI: .__searchQueries["hasCI"]
hasDockerfile: .__searchQueries["hasDockerfile"]
url: .web_url
The searchQueries feature:
- Executes search queries using the GitLab Advanced Search API.
- Stores results in the
__searchQueriesobject in the raw data as boolean values (trueif matches found,falseotherwise). - Supports multiple search scopes (e.g.,
blobs,commits,wiki_blobs). - Handles errors gracefully (stores
nullif a query fails). - Applies to the
projectkind only,project-with-membersdoes not supportsearchQueries.
Each search query requires:
name- A unique name for this search query, used as the key in__searchQueries.scope- The GitLab search scope (defaults toblobsif not specified).query- The search query string following GitLab Advanced Search syntax.
The search:// prefix in mappings is deprecated and will be removed in a future version. Use the searchQueries selector instead.
The search:// prefix will continue to work but will show deprecation warnings in the logs.
Ingest files from your repositories
Port allows you to fetch JSON and YAML files from your repositories, and create entities from them in your software catalog.
This is done using the file kind in your GitLab mapping configuration.
For example, say you want to manage your package.json files in Port.
You will need to create a manifest blueprint, with each of its entities representing a package.json file.
The following configuration fetches all package.json files from my-project and my-other-project, and creates an entity for each of them, based on the manifest blueprint:
resources:
- kind: file
selector:
query: 'true'
files:
path: 'package.json'
# Optional. Defaults to groupSearch.
# Use repositoryTree when you need glob paths or complete results without relying on GitLab's search index.
searchStrategy: groupSearch
repos:
# Optional: omit to search across all top-most level groups you have access to
# Note: with groupSearch/projectSearch, repositories not part of any group are excluded
# Replace with your repository's path_with_namespace (e.g., "group/project" or "group/subgroup/project")
- group/my-project
- group/my-other-project
Search strategy
The files.searchStrategy selector controls how the integration discovers files:
| Strategy | How it works | Best for |
|---|---|---|
groupSearch (default) | Queries GitLab's Advanced Search across accessible groups. | Fast discovery of exact filenames or simple * patterns. |
projectSearch | Queries Advanced Search per project. | Broad repository scans when group search is too coarse. |
repositoryTree | Walks each project's Git tree via the repository tree API. Does not depend on GitLab's search index. | Glob paths (**/, nested dirs), dot-directories, and complete results when search indexing is stale or disabled. |
When Advanced Search / blobs scope is unavailable for a group, groupSearch falls back to project-level search and caches that for the process lifetime — so the table is not “Advanced Search or nothing.”
repositoryTreeUse repositoryTree when Advanced Search misses files (common for paths under .cursor/, .claude/, and similar dot-directories), or when you need recursive globs such as infra/**/*.tf. Tree walks are more complete and consistent, but slower than search-based strategies.
resources:
- kind: file
selector:
query: 'true'
files:
# Prefer a fixed prefix (e.g. `.cursor/skills/...`) so the tree walk is scoped.
path: '.cursor/skills/**/SKILL.md'
searchStrategy: repositoryTree
repos:
- group/my-skills-repo
For Agent Skills specifically, prefer the native skill kind instead of file + repositoryTree.
Path field capabilities
Path syntax depends on the selected search strategy.
groupSearch and projectSearch
These strategies use GitLab's Advanced Search syntax, which differs from glob patterns. The search supports:
- Simple
*wildcards for filename patterns:*.tf,package.json,test_*. - Exact file paths:
src/app/main.py,infra/terraform/main.tf.
The search doesn't support:
- Path wildcards like
infra/terraform/*.tf. - Recursive patterns like
**/filename. - Complex patterns like
*.{js,ts},[abc]*, and!exclude.
For complex patterns, either switch to repositoryTree or split them into multiple file kinds with exact paths.
repositoryTree
With repositoryTree, path is a glob pattern matched against repository tree paths. This supports:
- Recursive globs:
**/package.json,infra/**/*.tf. - Nested directories:
.cursor/skills/**/SKILL.md. - Dot-directories that Advanced Search often under-indexes.
The integration scopes each tree walk to the pattern's fixed path prefix (patterns that start with **/ walk the full tree). The file kind still uses one files.path per resource; skill and plugin kinds share this tree matcher and can evaluate multiple patterns per repository in one walk.
After adding the file kind to your mapping configuration, click on the Resync button.
When you open the mapping configuration again, you will see real examples of files fetched from your GitLab organization.
This will help you see what data is available to use in your jq expressions.
Click on the Test mapping button to test your mapping against the example data.
The structure of the available data is as follows:Available data example (click to expand)
Create multiple entities from a single file
In some cases, we want to parse a single JSON or YAML file and create multiple entities from it.
To do this, we can use the itemsToParse key in our mapping configuration.
For example, let's say we want to track or manage a project's dependencies in Port.
We'll need to create a package blueprint, with each entity representing a dependency from a package.json file.
The following configuration fetches a package.json file from a specific repository and creates an entity for each dependency in the file, based on the package blueprint:
resources:
- kind: file
selector:
query: 'true'
files:
path: 'package.json'
# Note that in this case we are fetching from a specific repository
repos:
- group/my-project
port:
itemsToParse: .file.content.dependencies | to_entries
entity:
mappings:
# Since identifier cannot contain special characters, we are using jq to remove them
identifier: >-
.item.key + "_" + if (.item.value | startswith("^")) then
.item.value[1:] else .item.value end
title: .item.key + "@" + .item.value
blueprint: '"package"'
properties:
package: .item.key
version: .item.value
relations: {}
The itemsToParse key is used to specify the path to the array of items you want to parse from the file.
In this case, we are parsing the dependencies object from the package.json file.
Once the object is parsed, we can use the item key to refer to each key-value pair within it — where the key is the dependency name, and the value is the version.
This allows us to create an entity for each dependency dynamically.
Multi-document YAML files
For multi-document YAML files (a single file containing multiple YAML documents separated by ---), .file.content will not resolve to an object, but to an array of objects.
You can use one of these methods to ingest multi-document YAML files:
- Use the
itemsToParsekey to create multiple entities from such a file (see example above). - Map the result to an
arrayproperty.
If you have both single-document and multi-document YAML files in your repositories, you can use the itemsToParse key like this to handle both cases:
itemsToParse: .file.content | if type== "object" then [.] else . end
Limitations
- Currently only files up to 1MB in size are supported.
- Only JSON and YAML formats are automatically parsed.
Other file formats can be ingested as raw files, however, some special characters in the file (such as
\n) may be processed and not preserved. - Currently only the default branch of the repository is supported.
- With
groupSearchorprojectSearch, when you omit thereposfield the integration searches across all top-most level groups you have access to. Repositories that are not part of any group are excluded. - With
repositoryTree, discovery walks accessible projects via the GitLab projects API (optionally scoped withrepos). Tree walks are slower than Advanced Search on large instances.
For a list of known limitations with GitLab's Advanced Search, see GitLab's Advanced Search documentation.
For practical examples of using the file kind, see the file kind examples page.
Ingest agent skills
Port can discover Agent Skills (SKILL.md files) from your GitLab projects using the dedicated skill kind. Each matched SKILL.md becomes one catalog entity with parsed frontmatter and markdown instructions.
Skill discovery always walks the repository tree (same mechanism as files.searchStrategy: repositoryTree). Glob paths are supported. Tree walks are slower than Advanced Search on large instances. Without paths[].repos, the integration scans every accessible project — scope with repos when you can. Skill/plugin inherit GroupSelector; includeOnlyActiveGroups is mapped into project list params, so it filters active projects for tree walks despite the groups-oriented name.
Default discovery paths (override with selector.paths):
.agents/skills/**/SKILL.md.agent/skills/**/SKILL.md.cursor/skills/**/SKILL.md.claude/skills/**/SKILL.md.codex/skills/**/SKILL.md.github/skills/**/SKILL.md.opencode/skills/**/SKILL.mdskills/**/SKILL.md
resources:
- kind: skill
selector:
query: 'true'
# Optional. Defaults to the paths listed above.
# Each paths[] entry is independent: omitting repos on an entry
# walks every accessible project for that pattern (the repos list
# on another entry does not apply to it).
paths:
- path: '.cursor/skills/**/SKILL.md'
repos:
- group/my-skills-repo
- path: 'skills/**/SKILL.md'
repos:
- group/my-skills-repo
port:
entity:
mappings:
identifier: .repo.path_with_namespace + "/" + .skill.skillMdPath
title: .skill.name // .skill.skillMdPath
blueprint: '"skill"'
properties:
description: .skill.description
instructions: .skill.instructions
path: .skill.skillMdPath
root: .skill.root
repo: .repo.path_with_namespace
repoUrl: .repo.web_url
branch: .__branch
source: '"gitlab"'
After adding the skill kind and clicking Resync, open the mapping again to inspect live skill examples, then use Test mapping to validate your JQ.
Available data example (click to expand)
{
"skill": {
"name": "hello-skill",
"description": "A minimal example skill used to test skill discovery.",
"instructions": "# Hello Skill\n\nRespond with a short greeting.\n",
"frontmatter": {
"name": "hello-skill",
"description": "A minimal example skill used to test skill discovery."
},
"path": "skills/hello-skill",
"skillMdPath": "skills/hello-skill/SKILL.md",
"root": "skills"
},
"repo": {
"id": 1,
"name": "example-skills",
"path_with_namespace": "acme/example-skills",
"default_branch": "main",
"web_url": "https://gitlab.com/acme/example-skills"
},
"__branch": "main"
}
Resync and push webhooks for skills both use the project's default branch. Branch deletes are ignored so catalog entities stay tied to default-branch content.
For skills registry concepts and Port AI loading, see the skills registry.
Ingest agent plugins
The plugin kind detects agent plugin packages in a repository (for example Claude, Cursor, Codex, OpenCode) and emits one entity per repository, merging the provider manifests that are present.
Discovery uses the repository tree API (same as skills). Tree walks are slower than Advanced Search on large instances. Without selector.repos, the integration scans every accessible project — scope with repos when you can. Like skills, includeOnlyActiveGroups filters active projects for these tree walks.
Configure which providers to detect with selector.providers (defaults to all supported providers). Optionally scope with selector.repos (path_with_namespace values).
| Provider | Paths detected |
|---|---|
claude | .claude-plugin/plugin.json, .claude-plugin/marketplace.json |
cursor | .cursor-plugin/plugin.json |
codex | .codex-plugin/plugin.json |
agents | .agents/plugins/marketplace.json |
kimi | .kimi-plugin/plugin.json |
opencode | files under .opencode/plugins/ |
pi | files under .pi/extensions/ |
antigravity | gemini-extension.json |
resources:
- kind: plugin
selector:
query: 'true'
providers:
- claude
- cursor
- opencode
# Optional project path_with_namespace filter
repos:
- group/my-plugin-repo
port:
entity:
mappings:
identifier: .repo.path_with_namespace
title: .plugin.displayName // .plugin.name
blueprint: '"agentPlugin"'
properties:
description: .plugin.description
version: .plugin.version
url: .repo.web_url
repo: .repo.path_with_namespace
source: '"gitlab"'
supportsClaudeCode: .plugin.supports.claude
supportsCursor: .plugin.supports.cursor
supportsCodex: .plugin.supports.codex
supportsAgents: .plugin.supports.agents
supportsKimi: .plugin.supports.kimi
supportsOpenCode: .plugin.supports.opencode
supportsPi: .plugin.supports.pi
supportsAntigravity: .plugin.supports.antigravity
Available data example (click to expand)
{
"plugin": {
"name": "superpowers",
"displayName": "Superpowers",
"description": "Core skills library",
"version": "6.1.1",
"supports": {
"claude": true,
"cursor": true,
"codex": true,
"agents": true,
"kimi": true,
"opencode": true,
"pi": true,
"antigravity": true
},
"claude": {
"name": "superpowers",
"marketplaceName": "superpowers-dev"
},
"cursor": {
"name": "superpowers",
"displayName": "Superpowers"
},
"codex": {
"name": "superpowers"
},
"agents": {
"name": "superpowers"
},
"kimi": {
"name": "superpowers"
},
"opencode": {
"detected": true
},
"pi": {
"detected": true
},
"antigravity": {
"name": "superpowers"
}
},
"repo": {
"id": 2,
"name": "superpowers",
"path_with_namespace": "obra/superpowers",
"default_branch": "main",
"web_url": "https://gitlab.com/obra/superpowers"
},
"__branch": "main"
}
The raw payload always includes every provider key (claude … antigravity): a manifest object when present, { "detected": true } for directory-only providers (opencode / pi), or {} when that provider is unsupported in the project.
Resync and push webhooks for plugins both use the project's default branch. Branch deletes are ignored so catalog entities stay tied to default-branch content.