Types
Every shape the kit hands you is typed, from a list item to each section's data. This page covers where to import the types from and what the main ones hold. The declarations in the package are the full reference; this is the map.
Where to import them
| From | You get |
|---|---|
@sonordev/agency-site-kit/portfolio | The case study types: items, sections, each section's data, KPIs, metrics, ProofRecord and PORTFOLIO_SECTION_TYPES, plus HeadlineKpi, SoloDeviceInput, PortfolioJsonLdOptions and ResolvedPortfolioImage |
@sonordev/agency-site-kit | All of the above, plus the item-level types inside sections (PortfolioChallengeItem, PortfolioResultItem, PortfolioGalleryImage and so on), BrandConfig, SanityImageRef and PortfolioConfigResponse |
@sonordev/agency-site-kit/layout | AgencySiteKitLayoutProps |
@sonordev/agency-site-kit/portfolio/server | PortfolioSitemapEntry |
@sonordev/agency-site-kit/revalidate | CreateRevalidateHandlerOptions |
@sonordev/agency-site-kit/preview | PortfolioPreviewHandlerOptions |
The root entry is server only, but a type import is erased at build time, so
import type { PortfolioResultItem } from '@sonordev/agency-site-kit' is fine
in a client component.
Case studies
PortfolioItem
A published case study as the list returns it.
| Field | Type | What it is |
|---|---|---|
id, slug, title, subtitle | string | |
description | string | |
category | string | Stored as a slug. Print it with formatCategoryLabel. |
industry_tags | string[], optional | Industry categories for industry pages, primary first |
services | string[] | Service tags. Print them with formatServiceTag. |
hero_image, hero_image_alt | string | |
hero_screenshots | { desktop?, tablet?, mobile? } or null | Device screenshots, as URLs |
showcase_sites | array or null, optional | One entry per site when the case study covers several (see Sections and devices) |
live_url | string or null | |
kpis | PortfolioKPI[] | The headline numbers |
details | PortfolioDetailsData or null | Industry, location, website, timeline, launch date, budget range |
seo | PortfolioSeoData or null | Meta title, meta description, keywords |
featured | boolean | |
order | number, optional | |
published_at | string or null | ISO timestamp |
project_id | string or null | The Sonor project it was built from, if any |
baseline_metrics, current_metrics | MetricsSnapshot or null | Search, Core Web Vitals, analytics and page counts, before and now |
metrics_last_refreshed_at | string or null | When the live metrics last updated |
PortfolioItemFull
What getPortfolioItem returns: a PortfolioItem plus
| Field | Type | What it is |
|---|---|---|
sections | PortfolioSection[] | The case study's content (see below) |
metricsDelta | MetricsDelta[] | Live analytics changes. Run them through gateMetricsDeltas before showing any. |
sectionsOrdered | boolean, optional | True once someone reordered the sections in the dashboard. orderedSections reads it. |
PortfolioListResponse
{ items: PortfolioItem[]; total: number; limit: number; offset: number },
from getPortfolioItems.
Numbers
PortfolioKPI
interface PortfolioKPI {
label: string;
value: number;
suffix: string;
prefix?: string;
description: string;
source: MetricSource; // 'measured' | 'reported' | 'estimated'
reportedBy?: MetricAttribution; // required when source is 'reported'
}A results card (PortfolioResultItem) carries the same source and
reportedBy, with its figure in metric: { value, suffix, prefix? }.
MetricAttribution is { name, role?, organization, date }, with date as
YYYY-MM-DD. Proof and provenance covers what each source may
claim.
MetricsDelta
interface MetricsDelta {
metric: string;
baseline: number;
current: number;
delta: number;
deltaPercent: number;
direction: 'up' | 'down' | 'flat'; // 'up' means improved, even for lower-is-better metrics
timespan: string;
}MetricsSnapshot
Four groups of optional numbers: seo (clicks_28d, impressions_28d,
avg_position, ctr), cwv (lcp_ms, inp_ms, cls,
performance_score), analytics (sessions_28d, page_views_28d,
conversion_rate, bounce_rate) and pages (total_indexed,
total_crawled, total_pages).
Sections
PortfolioSection<T> is { sectionType, displayName, data }, where data
is typed by PortfolioSectionDataMap[sectionType]. PortfolioSectionType is
the union of the names in PORTFOLIO_SECTION_TYPES, and
PortfolioSectionData is the union of every data shape.
sectionType | Data type | Main fields |
|---|---|---|
portfolioHero | PortfolioHeroData | headline, subheadline, description, category, services, liveUrl?, screenshots, kpis |
portfolioChallenges | PortfolioChallengesData | items: { title, description, solution, result?, icon? } |
portfolioStrategy | PortfolioStrategyData | phases: { number, title, description, deliverables?, icon?, timeline? } |
portfolioResults | PortfolioResultsData | items: PortfolioResultItem |
portfolioTechStack | PortfolioTechStackData | technologies: { name, category, icon?, description? } |
portfolioServices | PortfolioServicesData | items: { title, description, features, icon? } |
portfolioTestimonial | PortfolioTestimonialData | quote, author, title, company, avatar?, rating? |
portfolioGallery | PortfolioGalleryData | images: { image, caption?, type? }, layout? |
portfolioVideo | PortfolioVideoData | url?, embedUrl?, thumbnail, title, duration?, platform? |
portfolioTeam | PortfolioTeamData | members: { name, role, avatar? } |
portfolioFeatureSpotlight | PortfolioFeatureSpotlightData | image, title, description, layout, annotations |
portfolioBeforeAfter | PortfolioBeforeAfterData | before, after, beforeUrl?, afterUrl?, beforeLabel?, afterLabel?, archiveUrl?, defaultPosition? |
portfolioMetricsTimeline | PortfolioMetricsTimelineData | dataPoints, baselineDate, title?, description? |
portfolioConversionFunnel | PortfolioConversionFunnelData | stages: { label, value, icon?, color? } |
portfolioPerformance | PortfolioPerformanceData | before?, after? (Lighthouse scores), cwv?, formFactor?, method?, measuredAt?, measuredUrl?, beforeMethod? |
portfolioDetails | PortfolioDetailsData | industry, location?, website?, timeline?, launchDate?, budgetRange? |
portfolioSeo | PortfolioSeoData | metaTitle, metaDescription, keywords, ogImage? |
portfolioCTA | PortfolioCTAData | headline, description, buttonText, buttonUrl, style? |
portfolioSpeedComparison, portfolioSiteArchitecture and
portfolioDesignSystem have no data type yet.
A few notes that save a bug:
- Image fields carry URLs.
avatar,image,thumbnail,before,afterandogImageare typed asSanityImageRef, the older image object, but hold plain URL strings at runtime. Pass them toresolvePortfolioImage, which takes either. The type stays so existing code keeps compiling. - Performance captions.
PortfolioPerformanceData'sformFactor,method,measuredAtandmeasuredUrlsay how the scores were taken. State them in the section's caption when they're present (printmeasuredAtwithformatIsoDate, and show only the host ofmeasuredUrl).beforeMethodbelongs next to before-and-after scores. - Before and after. Prefer
beforeUrlandafterUrlwhen they're set.beforeLabelsays where the old image came from (for example an archive capture) and belongs in its badge and alt text.
Evidence: ProofRecord
What getProofRecords returns, one per published piece of evidence.
| Field | Type | What it is |
|---|---|---|
id | string | |
site | string | The site the record is published on |
case_slug | string | The case study it belongs to |
metric, definition | string | What was counted, and how it's defined |
result_value, unit | number, string | The result |
window_start, window_end | string | The period it covers |
baseline_value, baseline_start, baseline_end | number / string, or null | What it's compared against, when there's a baseline |
source_url, source_label | string | Where it came from. A client-reported figure names who reported it in source_label. |
limitations | string | What it doesn't prove |
reviewed_at | string | When it was last reviewed |
Brand
BrandConfig is the agency brand AgencySiteKitLayout turns into CSS
variables: primary, secondary, background, backgroundElevated,
surface, surfaceHover, surfaceBorder, textPrimary, textSecondary,
textTertiary, fontHeading and fontBody, plus optional logoUrl,
logoAlt, radius ({ sm, md, lg }) and darkMode (colour overrides for
dark mode). See Brand layout.
PortfolioConfigResponse is the raw brand endpoint's answer:
{ brand: BrandConfig; orgName: string; orgSlug: string }.
Helper types
| Type | Used by |
|---|---|
HeadlineKpi | headlineKpi()'s result: { value, label, source, credit, kpi } |
SoloDeviceInput | shouldRenderSoloDevice() and resolveSoloScreenshot(): { category?, screenshots?, heroImage? } |
PortfolioJsonLdOptions | buildPortfolioJsonLd()'s options: { url, publisher? } |
ResolvedPortfolioImage | resolvePortfolioImage()'s result: { url, width?, height? } |
PortfolioSitemapEntry | generatePortfolioSitemapEntries()'s entries |
AgencySiteKitLayoutProps | Brand layout |
CreateRevalidateHandlerOptions, PortfolioPreviewHandlerOptions | Live updates and preview |