You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The GitHubRepository class includes a CustomProperties property that represents the custom property values assigned to a repository. These are organization-defined key-value pairs used for categorization and automation (e.g., Type, SubscribeTo, Environment).
When working with repositories in scripts or automation, custom properties are frequently accessed to make decisions — for example, filtering repositories by type or checking which notification channels a repository subscribes to.
Request
The CustomProperties property on GitHubRepository is currently typed as [GitHubCustomProperty[]] — an array of objects, each with a Name and Value property. Accessing a specific custom property requires filtering the array:
# Current (cumbersome)$repo.CustomProperties|Where-Object { $_.Name-eq'Type' } |Select-Object-ExpandProperty Value
The desired experience is to access custom property values directly as properties on an object:
This makes the interface consistent with how PowerShell users expect to interact with structured data — using dot-notation on objects rather than filtering arrays of Name-Value pairs.
Multi-select values are flattened to a single string
GitHub custom properties support a multi_select type where the API returns an array of values. The current GitHubCustomProperty class types Value as [string], which causes PowerShell to join the array elements into a single space-delimited string:
# Current behavior — multi-select values are flattened$repo.CustomProperties|Where-Object Name -eq SubscribeTo |Select-Object-ExpandProperty Value
# Returns: "CODEOWNERS Custom Instructions dependabot.yml gitattributes gitignore Hooks License Linter Settings Prompts PSModule Settings"# This is a single string, not an array — iterating produces one item$repo.CustomProperties|Where-Object Name -eq SubscribeTo |Select-Object-ExpandProperty Value |ForEach-Object { Write-Host"[$_]" }
# Output: [CODEOWNERS Custom Instructions dependabot.yml gitattributes gitignore Hooks License Linter Settings Prompts PSModule Settings]
The expected behavior is that multi-select values are [string[]] arrays, so each value can be iterated individually:
The values endpoint (/repos/{owner}/{repo}/properties/values) does not include the value_type in its response — only the property_name and value. The type distinction must be inferred from the value structure: multi_select values arrive as JSON arrays, true_false values arrive as the literal strings "true" or "false" (which must be converted to [bool]), and url values are detected as well-formed absolute URIs and converted to [uri]. All other scalar strings remain as [string].
$repo.CustomProperties is a [PSCustomObject] with dot-notation access
$repo.CustomProperties.Type returns 'Module' as [string] (single-select)
$repo.CustomProperties.Description returns 'This is a test' as [string] (string)
$repo.CustomProperties.SubscribeTo returns @('Custom Instructions', 'License', 'Prompts') as [string[]] (multi-select)
$repo.CustomProperties.Archive returns $true as [bool] (true/false)
$repo.CustomProperties.Upstream returns a [uri] with value https://github.com (url)
Multi-select values are preserved as arrays, not flattened to space-delimited strings
Properties with no value set return $null
The change applies to both REST API and GraphQL code paths in the GitHubRepository constructor
All five platform data types (string, single_select, multi_select, true_false, url) are correctly handled
Technical decisions
Type change for CustomProperties: Change the property type on GitHubRepository from [GitHubCustomProperty[]] to [PSCustomObject]. A PSCustomObject allows dynamic property names derived from the custom property names, enabling dot-notation access. This is the idiomatic PowerShell approach for dynamic key-value data.
Preserve value types from the API: The GitHub API returns multi-select custom property values as JSON arrays (e.g., ["CODEOWNERS", "Custom Instructions", "dependabot.yml"]). The current GitHubCustomProperty class declares [string] $Value, which causes PowerShell to coerce arrays to a single space-delimited string. With the new PSCustomObject approach, values must be stored with proper type handling — arrays stay as [string[]], scalar strings stay as [string], and null stays as $null.
true_false values are converted to [bool]: The GitHub API returns true_false custom property values as the literal strings "true" and "false". These are converted to [bool] ($true / $false) because boolean properties should be boolean in PowerShell. The conversion logic checks if the value equals "true" or "false" (case-insensitive) and converts accordingly. Since the values endpoint does not include value_type, this conversion is applied heuristically to values that are exactly "true" or "false". This is a safe heuristic because string and single_select properties that have "true" or "false" as a value would also benefit from boolean semantics, and the true_false type is the only type that constrains values to exactly these two strings.
url values are converted to [uri]: URL-type custom property values are converted to [uri] using [System.Uri]::new(). The detection heuristic uses [System.Uri]::IsWellFormedUriString($value, [System.UriKind]::Absolute) to identify well-formed absolute URIs before conversion. This correctly identifies values like https://github.com while leaving regular strings and single-select values untouched. The [uri] type is the idiomatic .NET/PowerShell representation for URLs and provides properties like Host, Scheme, AbsolutePath etc.
GitHubCustomProperty class: The existing GitHubCustomProperty class in src/classes/public/Repositories/GitHubCustomProperty.ps1 is no longer needed for the repository property. It may still be useful for Get-GitHubRepositoryCustomProperty output, so evaluate whether to keep it or also update that function to return a PSCustomObject. Decide during implementation — if the class is not used elsewhere, remove it.
Construction approach (REST API path): In the REST constructor branch ($null -ne $Object.node_id), the custom_properties field from the GitHub REST API is already a flat object with property names as keys and values that may be strings or arrays. Convert it directly with type-appropriate handling:
Construction approach (GraphQL path): In the GraphQL constructor branch, the data comes as repositoryCustomPropertyValues.nodes — an array of objects with propertyName and value. The value field may be a string or an array. Convert it to a flat PSCustomObject with the same type handling:
Get-GitHubRepositoryCustomProperty function: Update the output of this function to also return a PSCustomObject for consistency. The REST endpoint /repos/{owner}/{repo}/properties/values returns an array of { property_name, value } objects — convert to a flat PSCustomObject before returning, applying the same type handling (arrays for multi-select, [bool] for true/false, [uri] for URLs, strings for the rest).
Breaking change: This is a breaking change to the CustomProperties interface. Code that currently
iterates $repo.CustomProperties expecting .Name and .Value properties will need to be updated. However, the new interface is significantly more intuitive and aligns with PowerShell conventions. Consider whether this warrants a Major label — given that custom properties are a relatively new feature and the old interface was not widely documented, treating this as Minor is reasonable.
Test approach: Tests use the PSModule/GitHub repository which has custom properties configured covering all five value types (see Test data section above). Tests are added to tests/Repositories.Tests.ps1 within the existing auth-case-based Context block. The tests retrieve the PSModule/GitHub repo and verify each custom property via dot-notation, checking both value and type. Tests run for all auth cases that have access to read the repository.
Implementation plan
Core changes
Change the CustomProperties property type in GitHubRepository class from [GitHubCustomProperty[]] to [PSCustomObject] in src/classes/public/Repositories/GitHubRepository.ps1
Update the REST API constructor branch in GitHubRepository to convert custom_properties to a PSCustomObject, preserving array values for multi-select, converting "true"/"false" to [bool], and converting well-formed absolute URIs to [uri]
Update the GraphQL constructor branch in GitHubRepository to convert repositoryCustomPropertyValues.nodes to a PSCustomObject, preserving array values for multi-select, converting "true"/"false" to [bool], and converting well-formed absolute URIs to [uri]
Update Get-GitHubRepositoryCustomProperty in src/functions/public/Repositories/CustomProperties/Get-GitHubRepositoryCustomProperty.ps1 to return a PSCustomObject instead of raw API response
Cleanup
Evaluate whether GitHubCustomProperty class in src/classes/public/Repositories/GitHubCustomProperty.ps1 is still needed; remove if unused
Update any internal code that references CustomProperties as an array of GitHubCustomProperty objects
Tests
Tests use the PSModule/GitHub repo with preconfigured custom properties covering all five data types.
Add test verifying CustomProperties is a PSCustomObject (not an array) on PSModule/GitHub
Add test for Archive (true_false) — $repo.CustomProperties.Archive is [bool] and equals $true
Add test for Description (string) — $repo.CustomProperties.Description is [string] and equals 'This is a test'
Add test for SubscribeTo (multi_select) — $repo.CustomProperties.SubscribeTo is [string[]] and contains 'Custom Instructions', 'License', 'Prompts'
Add test for Type (single_select) — $repo.CustomProperties.Type is [string] and equals 'Module'
Add test for Upstream (url) — $repo.CustomProperties.Upstream is [uri] and equals 'https://github.com'
Add test for Get-GitHubRepositoryCustomProperty returning a PSCustomObject with the same values
Documentation
Update function help for Get-GitHubRepositoryCustomProperty with new output format
Add usage example showing dot-notation access: $repo.CustomProperties.Type
Context
The
GitHubRepositoryclass includes aCustomPropertiesproperty that represents the custom property values assigned to a repository. These are organization-defined key-value pairs used for categorization and automation (e.g.,Type,SubscribeTo,Environment).When working with repositories in scripts or automation, custom properties are frequently accessed to make decisions — for example, filtering repositories by type or checking which notification channels a repository subscribes to.
Request
The
CustomPropertiesproperty onGitHubRepositoryis currently typed as[GitHubCustomProperty[]]— an array of objects, each with aNameandValueproperty. Accessing a specific custom property requires filtering the array:The desired experience is to access custom property values directly as properties on an object:
This makes the interface consistent with how PowerShell users expect to interact with structured data — using dot-notation on objects rather than filtering arrays of Name-Value pairs.
Multi-select values are flattened to a single string
GitHub custom properties support a
multi_selecttype where the API returns an array of values. The currentGitHubCustomPropertyclass typesValueas[string], which causes PowerShell to join the array elements into a single space-delimited string:The expected behavior is that multi-select values are
[string[]]arrays, so each value can be iterated individually:Platform data types
The GitHub platform supports five custom property value types. The conversion must correctly handle all of them:
value_typestring"some text"[string]single_select"selected_value"[string]multi_select["val1", "val2"][string[]]true_false"true"or"false"[bool]url"https://..."[uri]Note
The values endpoint (
/repos/{owner}/{repo}/properties/values) does not include thevalue_typein its response — only theproperty_nameandvalue. The type distinction must be inferred from the value structure:multi_selectvalues arrive as JSON arrays,true_falsevalues arrive as the literal strings"true"or"false"(which must be converted to[bool]), andurlvalues are detected as well-formed absolute URIs and converted to[uri]. All other scalar strings remain as[string].Allowed characters and limits
Per the GitHub documentation:
a-z,A-Z,0-9,_,-,$,#only. No spaces. Maximum 75 characters.". Maximum 75 characters.Test data
The
PSModule/GitHubrepository has the following custom properties configured, covering all five value types:Archivetrue_falsetrue[bool] $trueDescriptionstringThis is a test[string] 'This is a test'SubscribeTomulti_selectCustom Instructions,License,Prompts[string[]] @('Custom Instructions', 'License', 'Prompts')Typesingle_selectModule[string] 'Module'Upstreamurlhttps://github.com[uri] 'https://github.com'Acceptance criteria
$repo.CustomPropertiesis a[PSCustomObject]with dot-notation access$repo.CustomProperties.Typereturns'Module'as[string](single-select)$repo.CustomProperties.Descriptionreturns'This is a test'as[string](string)$repo.CustomProperties.SubscribeToreturns@('Custom Instructions', 'License', 'Prompts')as[string[]](multi-select)$repo.CustomProperties.Archivereturns$trueas[bool](true/false)$repo.CustomProperties.Upstreamreturns a[uri]with valuehttps://github.com(url)$nullGitHubRepositoryconstructorstring,single_select,multi_select,true_false,url) are correctly handledTechnical decisions
Type change for
CustomProperties: Change the property type onGitHubRepositoryfrom[GitHubCustomProperty[]]to[PSCustomObject]. APSCustomObjectallows dynamic property names derived from the custom property names, enabling dot-notation access. This is the idiomatic PowerShell approach for dynamic key-value data.Preserve value types from the API: The GitHub API returns multi-select custom property values as JSON arrays (e.g.,
["CODEOWNERS", "Custom Instructions", "dependabot.yml"]). The currentGitHubCustomPropertyclass declares[string] $Value, which causes PowerShell to coerce arrays to a single space-delimited string. With the newPSCustomObjectapproach, values must be stored with proper type handling — arrays stay as[string[]], scalar strings stay as[string], andnullstays as$null.true_falsevalues are converted to[bool]: The GitHub API returnstrue_falsecustom property values as the literal strings"true"and"false". These are converted to[bool]($true/$false) because boolean properties should be boolean in PowerShell. The conversion logic checks if the value equals"true"or"false"(case-insensitive) and converts accordingly. Since the values endpoint does not includevalue_type, this conversion is applied heuristically to values that are exactly"true"or"false". This is a safe heuristic becausestringandsingle_selectproperties that have"true"or"false"as a value would also benefit from boolean semantics, and thetrue_falsetype is the only type that constrains values to exactly these two strings.urlvalues are converted to[uri]: URL-type custom property values are converted to[uri]using[System.Uri]::new(). The detection heuristic uses[System.Uri]::IsWellFormedUriString($value, [System.UriKind]::Absolute)to identify well-formed absolute URIs before conversion. This correctly identifies values likehttps://github.comwhile leaving regular strings and single-select values untouched. The[uri]type is the idiomatic .NET/PowerShell representation for URLs and provides properties likeHost,Scheme,AbsolutePathetc.GitHubCustomPropertyclass: The existingGitHubCustomPropertyclass insrc/classes/public/Repositories/GitHubCustomProperty.ps1is no longer needed for the repository property. It may still be useful forGet-GitHubRepositoryCustomPropertyoutput, so evaluate whether to keep it or also update that function to return aPSCustomObject. Decide during implementation — if the class is not used elsewhere, remove it.Construction approach (REST API path): In the REST constructor branch (
$null -ne $Object.node_id), thecustom_propertiesfield from the GitHub REST API is already a flat object with property names as keys and values that may be strings or arrays. Convert it directly with type-appropriate handling:Construction approach (GraphQL path): In the GraphQL constructor branch, the data comes as
repositoryCustomPropertyValues.nodes— an array of objects withpropertyNameandvalue. Thevaluefield may be a string or an array. Convert it to a flatPSCustomObjectwith the same type handling:Get-GitHubRepositoryCustomPropertyfunction: Update the output of this function to also return aPSCustomObjectfor consistency. The REST endpoint/repos/{owner}/{repo}/properties/valuesreturns an array of{ property_name, value }objects — convert to a flatPSCustomObjectbefore returning, applying the same type handling (arrays for multi-select,[bool]for true/false,[uri]for URLs, strings for the rest).Breaking change: This is a breaking change to the
CustomPropertiesinterface. Code that currentlyiterates
$repo.CustomPropertiesexpecting.Nameand.Valueproperties will need to be updated. However, the new interface is significantly more intuitive and aligns with PowerShell conventions. Consider whether this warrants aMajorlabel — given that custom properties are a relatively new feature and the old interface was not widely documented, treating this asMinoris reasonable.Test approach: Tests use the
PSModule/GitHubrepository which has custom properties configured covering all five value types (see Test data section above). Tests are added totests/Repositories.Tests.ps1within the existing auth-case-based Context block. The tests retrieve thePSModule/GitHubrepo and verify each custom property via dot-notation, checking both value and type. Tests run for all auth cases that have access to read the repository.Implementation plan
Core changes
CustomPropertiesproperty type inGitHubRepositoryclass from[GitHubCustomProperty[]]to[PSCustomObject]insrc/classes/public/Repositories/GitHubRepository.ps1GitHubRepositoryto convertcustom_propertiesto aPSCustomObject, preserving array values for multi-select, converting"true"/"false"to[bool], and converting well-formed absolute URIs to[uri]GitHubRepositoryto convertrepositoryCustomPropertyValues.nodesto aPSCustomObject, preserving array values for multi-select, converting"true"/"false"to[bool], and converting well-formed absolute URIs to[uri]Get-GitHubRepositoryCustomPropertyinsrc/functions/public/Repositories/CustomProperties/Get-GitHubRepositoryCustomProperty.ps1to return aPSCustomObjectinstead of raw API responseCleanup
GitHubCustomPropertyclass insrc/classes/public/Repositories/GitHubCustomProperty.ps1is still needed; remove if unusedCustomPropertiesas an array ofGitHubCustomPropertyobjectsTests
Tests use the
PSModule/GitHubrepo with preconfigured custom properties covering all five data types.CustomPropertiesis aPSCustomObject(not an array) onPSModule/GitHubArchive(true_false) —$repo.CustomProperties.Archiveis[bool]and equals$trueDescription(string) —$repo.CustomProperties.Descriptionis[string]and equals'This is a test'SubscribeTo(multi_select) —$repo.CustomProperties.SubscribeTois[string[]]and contains'Custom Instructions','License','Prompts'Type(single_select) —$repo.CustomProperties.Typeis[string]and equals'Module'Upstream(url) —$repo.CustomProperties.Upstreamis[uri]and equals'https://github.com'Get-GitHubRepositoryCustomPropertyreturning aPSCustomObjectwith the same valuesDocumentation
Get-GitHubRepositoryCustomPropertywith new output format$repo.CustomProperties.Type$repo.CustomProperties.SubscribeTo | ForEach-Object { ... }