# Apps SDK

> Source: https://agilitycms.com/docs/apps/apps-sdk

The Agility Apps SDK v2 (`@agility/app-sdk`) enables developers to build powerful custom applications that extend and integrate with Agility CMS. This SDK provides a comprehensive set of tools for creating custom fields, sidebars, dashboards, modals, and other interactive components within the Agility CMS interface.

Important: Your app doesn't need to implement all surfaces. You can create an app that provides just a single custom field, or only a sidebar, or any combination of surfaces that meets your needs. The SDK is designed to be flexible—implement only what you need.

## App Definition File

Every Agility App must include an app definition file located at `/.well-known/agility-app.json`. This JSON file defines the metadata, capabilities, configuration values, and surfaces that your app provides.

You only need to define the surfaces your app actually implements. If you're building a simple custom field, you only need to include the `fields` capability. If you're building a dashboard, you only need the relevant dashboard capability. The example below shows all possible capabilities, but your app can include just one or a subset of these.

### Structure

```
{
	"name": "Example App - SDK v2",
	"documentationLink": "https://github.com/agility/agility-cms-apps",
	"description": "An Example App to show what the Agility App SDK V2 is capable of.",
	"version": "1.0.0",
	"__sdkVersion": "2.0.0",
	"configValues": [
		{
			"label": "Config Value 1",
			"name": "configValue1",
			"type": "string"
		}
	],
	"connections": [
		{
			"name": "Agility API Offline Access",
			"icon": "https://cdn.aglty.io/content-manager/images/logo-triangle-only-yellow.svg",
			"url": "/oauth/agility-api-offline"
		}
	],
	"capabilities": {
		"fields": [
			{
				"name": "example-field",
				"label": "Example Field",
				"description": "An example field that shows a custom UI."
			}
		],
		"modals": [
			{
				"name": "example-modal",
				"label": "Example Modal Dialog",
				"description": "An example modal dialog."
			}
		],
		"contentListSidebar": {
			"description": "Shows example data or ui that could be useful on a content list."
		},
		"contentItemSidebar": {
			"description": "Shows example data or ui that could be useful on a content item."
		},
		"pageSidebar": {
			"description": "Shows example data or ui that could be useful on a page."
		},
		"homeDashboard": {
			"description": "Shows data and analytics relevant to the entire site."
		},
		"contentDashboard": {
			"description": "Shows data and analytics relevant to the Content section."
		},
		"pagesDashboard": {
			"description": "Shows data and analytics relevant to the Pages section."
		},
		"sections": [
			{
				"name": "customsection",
				"label": "Custom Section 1",
				"description": "A custom section."
			}
		],
		"items": [
			{
				"name": "customitem",
				"label": "Custom Item 1",
				"description": "A custom item."
			}
		],
		"installScreen": true,
		"uninstallHook": "/api/app-uninstall"
	}
}

```

### Key Properties

- name: The display name of your app
- version: Your app's version number
- __sdkVersion: The SDK version your app is built with (should be "2.0.0")
- configValues: Configuration values that can be set during app installation
- connections: OAuth or external service connections
- capabilities: Defines all the surfaces your app provides (include only what you need)
- installScreen: Whether to show a custom installation screen
- uninstallHook: API endpoint called when the app is uninstalled

### Simple App Examples

You don't need to include all capabilities. Here are some minimal examples:

Just a Custom Field:

```
{
	"name": "My Custom Field",
	"version": "1.0.0",
	"__sdkVersion": "2.0.0",
	"capabilities": {
		"fields": [
			{
				"name": "my-field",
				"label": "My Custom Field",
				"description": "A custom field for editing content."
			}
		]
	}
}

```

Just a Content Item Sidebar:

```
{
	"name": "Content Helper",
	"version": "1.0.0",
	"__sdkVersion": "2.0.0",
	"capabilities": {
		"contentItemSidebar": {
			"description": "Provides helpful information when editing content."
		}
	}
}

```

Just a Dashboard:

```
{
	"name": "Analytics Dashboard",
	"version": "1.0.0",
	"__sdkVersion": "2.0.0",
	"capabilities": {
		"homeDashboard": {
			"description": "Shows analytics for your site."
		}
	}
}

```

## SDK Surfaces

The following sections describe all available surfaces. Remember: you only need to implement the surfaces that make sense for your app.

### Custom Fields

Custom fields allow you to create entirely custom UI components for content editing. Fields can be configured on any content definition and provide a custom editing experience.

Location: `/app/fields/{field-name}/page.tsx`

Example:

```
"use client"

import { useAgilityAppSDK, contentItemMethods, useResizeHeight } from "@agility/app-sdk"

export default function ExampleField() {
	const { initializing, appInstallContext, fieldValue } = useAgilityAppSDK()
	const containerRef = useResizeHeight(10)

	if (initializing) return null

	return (
		<div>
			<div ref={containerRef}>
				<textarea
					className="w-full rounded border border-gray-300 p-4"
					value={fieldValue as string}
					onChange={(e) => contentItemMethods.setFieldValue({ value: e.target.value })}
				/>
			</div>
		</div>
	)
}

```

Key Capabilities:

- Access current field value via 
- Update field value with 
- Auto-resize field height using 
- Access parent content item with 
- Listen to other field changes with 
- Open asset selector with 
- Open modals with 

App Definition:

```
"fields": [
  {
    "name": "example-field",
    "label": "Example Field",
    "description": "An example field that shows a custom UI."
  }
]

```

### Sidebars

Sidebars provide contextual information and actions alongside different areas of the Agility CMS interface.

#### Content Item Sidebar

Appears when editing a specific content item.

Location: `/app/content-item-sidebar/page.tsx`

Example:

```
"use client"

import { contentItemMethods, openModal, useAgilityAppSDK, assetsMethods } from "@agility/app-sdk"
import { useEffect, useState } from "react"

export default function ContentItemSidebar() {
	const { initializing, locale, contentItem } = useAgilityAppSDK()
	const [heading, setHeading] = useState()

	useEffect(() => {
		setHeading(contentItem?.values.Heading)
	}, [contentItem, contentItem?.values.Heading])

	return (
		<div>
			<p>Heading: {heading}</p>
			<button
				onClick={async () => {
					await contentItemMethods.saveContentItem()
				}}
			>
				Save
			</button>
			<button
				onClick={async () => {
					await contentItemMethods.addFieldListener({
						fieldName: "Heading",
						onChange: (value) => setHeading(value)
					})
				}}
			>
				Add Field Listener
			</button>
		</div>
	)
}

```

Key Capabilities:

- Access current content item via 
- Get full content item data with 
- Save content item with 
- Monitor field changes with 
- Select assets with 
- Open modals with 

App Definition:

```
"contentItemSidebar": {
  "description": "Shows example data or ui that could be useful on a content item."
}

```

#### Content List Sidebar

Appears when viewing a list of content items.

Location: `/app/content-list-sidebar/page.tsx`

Example:

```
"use client"

import { openModal, useAgilityAppSDK, contentItemMethods, refresh } from "@agility/app-sdk"
import { useState } from "react"

export default function ContentListSidebar() {
	const { initializing, locale } = useAgilityAppSDK()
	const [selected, setSelectedItems] = useState<any>([])

	return (
		<div>
			<div>Selected: {JSON.stringify(selected)}</div>
			<button onClick={() => refresh()}>Refresh List</button>
			<button
				onClick={async () => {
					contentItemMethods.addSelectedItemListener({
						onChange: (items) => setSelectedItems(items)
					})
				}}
			>
				Add Selection Listener
			</button>
		</div>
	)
}

```

Key Capabilities:

- Monitor selected items with 
- Get selected items with 
- Refresh the content list with 
- Remove selection listener with 

App Definition:

```
"contentListSidebar": {
  "description": "Shows example data or ui that could be useful on a content list."
}

```

#### Page Sidebar

Appears when editing a page in the Pages section.

Location: `/app/page-sidebar/page.tsx`

Example:

```
"use client"

import { useAgilityAppSDK, pageMethods, assetsMethods } from "@agility/app-sdk"
import { useState } from "react"

export default function PagesSidebar() {
	const { initializing, locale, pageItem } = useAgilityAppSDK()
	const [page, setPage] = useState<IPageItem | null>()

	return (
		<div>
			<button
				onClick={async () => {
					const pageData = await pageMethods.getPageItem()
					setPage(pageData)
				}}
			>
				Get Page Item
			</button>
			<div>Page: {JSON.stringify(page)}</div>
		</div>
	)
}

```

Key Capabilities:

- Access current page via 
- Get full page data with 
- Select assets with 
- Open modals with 

App Definition:

```
"pageSidebar": {
  "description": "Shows example data or ui that could be useful on a page."
}

```

### Dashboards

Dashboards provide high-level analytics and actions for different sections of Agility CMS.

#### Home Dashboard

Main dashboard visible on the home screen.

Location: `/app/home-dashboard/page.tsx`

#### Content Dashboard

Dashboard visible in the Content section.

Location: `/app/content-dashboard/page.tsx`

#### Pages Dashboard

Dashboard visible in the Pages section.

Location: `/app/pages-dashboard/page.tsx`

Common Example:

```
"use client"

import { useAgilityAppSDK, useResizeHeight, assetsMethods, openModal } from "@agility/app-sdk"

export default function Dashboard() {
	const { initializing, locale, appInstallContext } = useAgilityAppSDK()
	const ref = useResizeHeight()

	return (
		<div ref={ref}>
			<h1>Dashboard</h1>
			<div>Locale: {locale}</div>
			<div>Config: {JSON.stringify(appInstallContext?.configuration)}</div>
			<button
				onClick={() => {
					assetsMethods.selectAssets({
						title: "Select Assets",
						singleSelectOnly: false,
						callback: (assets) => console.log(assets)
					})
				}}
			>
				Select Assets
			</button>
		</div>
	)
}

```

Key Capabilities:

- Access app configuration via 
- Get configuration values with 
- Auto-resize dashboard with 
- Select assets with 
- Open modals with 

App Definition:

```
"homeDashboard": {
  "description": "Shows data and analytics relevant to the entire site."
},
"contentDashboard": {
  "description": "Shows data and analytics relevant to the Content section."
},
"pagesDashboard": {
  "description": "Shows data and analytics relevant to the Pages section."
}

```

### Modals

Modals are dialog windows that can be opened from any other surface in your app.

Location: `/app/modals/{modal-name}/page.tsx`

Example:

```
"use client"

import { closeModal, useAgilityAppSDK } from "@agility/app-sdk"

export default function ExampleModal() {
	const { initializing, modalProps } = useAgilityAppSDK()

	if (initializing) return <div>Initializing...</div>

	return (
		<div className="flex h-full flex-col">
			<h2>Example Modal</h2>
			<div className="flex-1">
				<div>Modal Props: {JSON.stringify(modalProps)}</div>
			</div>
			<div className="flex gap-2">
				<button onClick={() => closeModal({ btn: "ok" })}>OK</button>
				<button onClick={() => closeModal({ btn: "cancel" })}>Cancel</button>
			</div>
		</div>
	)
}

```

Opening a Modal:

```
openModal({
	title: "Example Modal",
	name: "example-modal",
	props: {
		customData: "value",
		timestamp: new Date()
	},
	callback: (result) => {
		console.log("Modal returned:", result)
	}
})

```

Key Capabilities:

- Access props passed from caller via 
- Close modal and return data with 
- Receive callback when modal closes

App Definition:

```
"modals": [
  {
    "name": "example-modal",
    "label": "Example Modal Dialog",
    "description": "An example modal dialog."
  }
]

```

### Custom Sections

Custom sections allow you to create entirely custom page modules that editors can add to pages.

Location: `/app/sections/{section-name}/page.tsx`

Example:

```
export default function CustomSection() {
	return <div>Custom Section</div>
}

```

App Definition:

```
"sections": [
  {
    "name": "customsection",
    "label": "Custom Section 1",
    "description": "A custom section."
  }
]

```

### Custom Items

Custom items provide a completely custom UI for managing content items.

Location: `/app/items/{item-name}/page.tsx`

Example:

```
export default function CustomItem() {
	return <div>Custom Item</div>
}

```

App Definition:

```
"items": [
  {
    "name": "customitem",
    "label": "Custom Item 1",
    "description": "A custom item."
  }
]

```

### Flyouts

Flyouts are small popup interfaces that can be triggered from various locations in Agility CMS.

Location: `/app/flyout/page.tsx`

Example:

```
"use client"

import { useAgilityAppSDK, assetsMethods } from "@agility/app-sdk"

export default function Flyout() {
	const { initializing, appInstallContext, instance, locale } = useAgilityAppSDK()

	return (
		<div>
			<h1>Flyout</h1>
			<div>Locale: {locale}</div>
			<div>Instance: {JSON.stringify(instance)}</div>
		</div>
	)
}

```

## Core Hooks

### useAgilityAppSDK()

The primary hook for accessing SDK functionality. Returns different properties depending on the surface.

Common Properties:

```
const {
	initializing, // Boolean: SDK initialization state
	locale, // String: Current locale (e.g., "en-us")
	appInstallContext, // Object: App installation context
	instance // Object: Current instance information
} = useAgilityAppSDK()

```

Field-Specific Properties:

```
const {
	field, // Object: Field definition
	fieldValue, // Any: Current field value
	contentItem // Object: Parent content item
} = useAgilityAppSDK()

```

Content Item Sidebar Properties:

```
const {
	contentItem // Object: Current content item
} = useAgilityAppSDK()

```

Page Sidebar Properties:

```
const {
	pageItem // Object: Current page item
} = useAgilityAppSDK()

```

Modal Properties:

```
const {
	modalProps // Object: Props passed when opening the modal
} = useAgilityAppSDK()

```

### useResizeHeight()

Automatically adjusts the iframe height to fit content. Essential for fields and dashboards.

Usage:

```
const containerRef = useResizeHeight(10) // 10px padding

return <div ref={containerRef}>Content</div>

```

Parameters:

- padding

### useAgilityPreInstall()

Used on the installation screen to handle app setup.

Usage:

```
import { setExtraConfigValues, useAgilityPreInstall } from "@agility/app-sdk"

export default function Install() {
	const { initializing, appInstallContext, instance, locale } = useAgilityPreInstall()

	return (
		<div>
			<h1>Install Screen</h1>
			<button
				onClick={() => {
					setExtraConfigValues([{ name: "apiKey", value: "xyz123" }])
				}}
			>
				Complete Install
			</button>
		</div>
	)
}

```

Key Methods:

- setExtraConfigValues(values)

## SDK Methods

### Content Item Methods

```
import { contentItemMethods } from "@agility/app-sdk"

// Get current content item
const item = await contentItemMethods.getContentItem()

// Save content item
await contentItemMethods.saveContentItem()

// Set field value (in custom fields)
contentItemMethods.setFieldValue({ value: "new value" })

// Listen to field changes
await contentItemMethods.addFieldListener({
	fieldName: "Title",
	onChange: (value) => console.log("Title changed:", value)
})

// Remove field listener
await contentItemMethods.removeFieldListener({
	fieldName: "Title"
})

// Get selected items (in content list sidebar)
const selected = await contentItemMethods.getSelectedItems()

// Listen to selection changes (in content list sidebar)
contentItemMethods.addSelectedItemListener({
	onChange: (items) => console.log("Selection changed:", items)
})

// Remove selection listener
contentItemMethods.removeSelectedItemListener()

```

### Page Methods

```
import { pageMethods } from "@agility/app-sdk"

// Get current page item
const page = await pageMethods.getPageItem()

```

### Assets Methods

```
import { assetsMethods } from "@agility/app-sdk"

// Select assets
assetsMethods.selectAssets({
	title: "Select Images",
	singleSelectOnly: false, // Allow multiple selection
	callback: (selectedAssets) => {
		console.log("Selected:", selectedAssets)
		// selectedAssets is an array of asset objects
	}
})

```

Asset Object Structure:

```
{
  mediaID: number,
  url: string,
  label: string,
  fileSize: number,
  // ... other properties
}

```

### Modal Methods

```
import { openModal, closeModal } from "@agility/app-sdk"

// Open a modal
openModal({
	title: "My Modal",
	name: "example-modal", // Must match modal name in app definition
	props: {
		// Custom data to pass to modal
		userId: 123,
		action: "edit"
	},
	callback: (result) => {
		// Called when modal closes
		console.log("Modal result:", result)
	}
})

// Close modal (from within the modal)
closeModal({
	success: true,
	data: "some return value"
})

```

### Config Methods

```
import { configMethods } from "@agility/app-sdk"

// Get a configuration value
const apiKey = configMethods.getConfigValue("apiKey")

```

### Other Methods

```
import { refresh } from "@agility/app-sdk"

// Refresh the current list (in content list sidebar)
refresh()

```

## Installation and Configuration

### Pre-Installation Screen

If your app requires setup before installation, enable the installation screen.

Location: `/app/install/page.tsx`

App Definition:

```
"installScreen": true

```

Example:

```
import { setExtraConfigValues, useAgilityPreInstall } from "@agility/app-sdk"

export default function Install() {
	const { initializing, appInstallContext } = useAgilityPreInstall()

	return (
		<div>
			<h1>Setup Instructions</h1>
			<p>Configure your app settings...</p>
			<button
				onClick={() => {
					setExtraConfigValues([
						{ name: "setting1", value: "value1" },
						{ name: "setting2", value: "value2" }
					])
				}}
			>
				Complete Installation
			</button>
		</div>
	)
}

```

### Configuration Values

Define configuration values in your app definition that can be set during installation.

```
"configValues": [
  {
    "label": "API Key",
    "name": "apiKey",
    "type": "string"
  },
  {
    "label": "Enable Feature",
    "name": "enableFeature",
    "type": "boolean"
  }
]

```

Accessing Configuration:

```
const { appInstallContext } = useAgilityAppSDK()
const apiKey = appInstallContext?.configuration?.apiKey

```

### Uninstall Hook

Handle app uninstallation cleanup.

App Definition:

```
"uninstallHook": "/api/app-uninstall"

```

API Route (`/app/api/app-uninstall/route.ts`):

```
export async function POST(request: Request) {
	const body = await request.json()

	// Clean up resources
	// Delete stored data
	// Revoke tokens

	return new Response("OK", { status: 200 })
}

```

## OAuth Integration

### Setting Up OAuth

Apps can integrate with external services using OAuth.

App Definition:

```
"connections": [
  {
    "name": "Agility API Offline Access",
    "icon": "https://cdn.aglty.io/logo.svg",
    "url": "/oauth/agility-api-offline"
  }
]

```

OAuth Flow (`/app/oauth/agility-api-offline/page.tsx`):

```
"use client"

import { useEffect } from "react"

export default function AgilityAPI() {
	useEffect(() => {
		const params = new URLSearchParams(window.location.search)
		const code = params.get("code")
		const redirect_uri = params.get("redirect_uri")

		if (code) {
			// Exchange code for token
			const formData = new FormData()
			formData.append("code", code)

			fetch("https://mgmt.aglty.io/oauth/token", {
				method: "POST",
				body: formData
			})
				.then((res) => res.text())
				.then((token) => {
					// Redirect back with token
					window.location.href = `${redirect_uri}#${encodeURIComponent(token)}`
				})
		} else {
			// Start OAuth flow
			const authUrl = `https://mgmt.aglty.io/oauth/authorize?response_type=code&redirect_uri=${encodeURIComponent(
				window.location.href
			)}&scope=offline_access`
			window.location.href = authUrl
		}
	}, [])

	return <div>Authenticating...</div>
}

```

## TypeScript Types

The SDK provides comprehensive TypeScript types:

```
import type { IContentItem, IPageItem, IAppConfigValue, IAppInstallContext, IField, IAsset } from "@agility/app-sdk"

```

## Best Practices

1. Always check 

```
if (initializing) return <div>Loading...</div>

```

1. Use 

```
const ref = useResizeHeight()
return <div ref={ref}>...</div>

```

1. Clean up listeners: Remove field and selection listeners when component unmounts

```
useEffect(() => {
	return () => {
		contentItemMethods.removeFieldListener({ fieldName: "Title" })
	}
}, [])

```

1. Handle errors gracefully: SDK methods may fail, always handle errors

```
try {
	await contentItemMethods.saveContentItem()
} catch (error) {
	console.error("Failed to save:", error)
}

```

1. Type your components: Use TypeScript for better development experience

```
const [item, setItem] = useState<IContentItem | null>(null)

```

## Complete Example

Here's a complete custom field example demonstrating multiple SDK features:

```
"use client"

import { useAgilityAppSDK, contentItemMethods, useResizeHeight, assetsMethods, openModal } from "@agility/app-sdk"
import { useEffect, useState } from "react"

export default function AdvancedField() {
	const { initializing, fieldValue, contentItem } = useAgilityAppSDK()
	const containerRef = useResizeHeight(10)
	const [relatedTitle, setRelatedTitle] = useState("")

	// Listen to another field
	useEffect(() => {
		contentItemMethods.addFieldListener({
			fieldName: "Title",
			onChange: (value) => setRelatedTitle(value as string)
		})

		return () => {
			contentItemMethods.removeFieldListener({ fieldName: "Title" })
		}
	}, [])

	if (initializing) return null

	const handleSelectAsset = () => {
		assetsMethods.selectAssets({
			title: "Select Image",
			singleSelectOnly: true,
			callback: (assets) => {
				if (assets.length > 0) {
					contentItemMethods.setFieldValue({ value: assets[0].url })
				}
			}
		})
	}

	const handleOpenModal = () => {
		openModal({
			title: "Configure Field",
			name: "field-config-modal",
			props: { currentValue: fieldValue },
			callback: (result) => {
				if (result?.value) {
					contentItemMethods.setFieldValue({ value: result.value })
				}
			}
		})
	}

	return (
		<div ref={containerRef} className="p-4">
			<div className="mb-2">
				<label>Current Value:</label>
				<input
					value={(fieldValue as string) || ""}
					onChange={(e) => contentItemMethods.setFieldValue({ value: e.target.value })}
					className="w-full rounded border p-2"
				/>
			</div>

			<div className="mb-2">
				<p>Related Title: {relatedTitle}</p>
			</div>

			<div className="flex gap-2">
				<button onClick={handleSelectAsset} className="btn">
					Select Asset
				</button>
				<button onClick={handleOpenModal} className="btn">
					Open Modal
				</button>
			</div>
		</div>
	)
}

```

## More Resources

- [Example App Repository](https://github.com/agility/agility-cms-apps/tree/main/example/example-sdk-v2)
