# linkapp add Source: https://docs.linktr.ee/cli/add Add components from the registry to your project ## Usage ```bash theme={"system"} npx @linktr.ee/linkapp add [options] ``` Adds a single UI component from the LinkApp registry into your project (powered by `npx shadcn@latest`). ## Options | Flag | Description | Default | | ------------------ | ----------------------------------------------------------------------------------- | ---------------- | | `--registry ` | Use a custom registry URL (defaults to LINKAPP\_REGISTRY\_URL or official registry) | default registry | | `--yes, -y` | Skip confirmation prompts | `false` | | `--overwrite` | Overwrite existing components | `false` | | `-h, --help` | Show help | - | ## Available components The default registry ships: * `button` * `container` * `embed` * `skeleton` * `switch` ## Prerequisites * Run from your LinkApp project root. * `components.json` must exist (created by create-linkapp). ## Examples Install the button component: ```bash theme={"system"} npx @linktr.ee/linkapp add button ``` Install from a custom registry with no prompts: ```bash theme={"system"} npx @linktr.ee/linkapp add container --registry https://example.com/r --yes ``` ## Overwriting components If a component already exists: ``` Component "button" already exists. Overwrite? (y/N) ``` Use `--overwrite` to skip the prompt: ```bash theme={"system"} npx @linktr.ee/linkapp add button --overwrite ``` ## Custom registries The CLI resolves the component URL from: 1. `--registry ` flag (if provided) 2. `LINKAPP_REGISTRY_URL` environment variable 3. Default registry: `https://create-linkapp-registry.vercel.app/r` You can also pass a full URL as the component argument: ``` npx @linktr.ee/linkapp add https://example.com/r/card.json ``` ## What happens 1. Validates you provided a component name and that `components.json` exists. 2. Resolves the component JSON from the registry. 3. Runs `npx shadcn@latest add ` with your flags. 4. Writes the component into `components/ui/` and prints an import hint. After a successful install you can import: ```ts theme={"system"} import { Button } from "@/components/ui/button"; ``` If the install fails, rerun with `--overwrite` or check the registry URL. # linkapp build Source: https://docs.linktr.ee/cli/build Create an optimized production build of your LinkApp ## Usage ```bash theme={"system"} npx @linktr.ee/linkapp build [options] ``` Run from your LinkApp project root. The command produces a production-ready bundle in `dist/`. ## Options | Flag | Description | Default | | ------------- | ---------------------------------------------------- | ------- | | `--sourcemap` | Emit source maps alongside the build for debugging | `false` | | `--profile` | Print profiling hints (alias: `--analyze`) | `false` | | `--compress` | Emit Brotli-compressed `.br` assets for CDN delivery | `false` | | `-h, --help` | Show usage information | - | `--analyze` is deprecated but works as an alias of `--profile`. ## What it does 1. Generates runtime assets in `.linkapp/` and builds with Rsbuild in production mode. 2. Copies `public/` into `dist/` and, if present, copies `app/icon.svg` to `dist/icon.svg`. 3. Writes content-hashed JS/CSS/assets plus `build-manifest.json` for asset mapping. 4. Prints a size summary (raw plus gzip/Brotli estimates for JS/CSS). 5. When profiling is requested, reminds you to inspect the bundle with your preferred tooling. ## Output ``` dist/ ├── index.html ├── build-manifest.json ├── icon.svg # only if app/icon.svg exists ├── assets/ │ ├── index-XXXXX.js │ ├── index-XXXXX.css │ └── vendor-XXXXX.js └── ...other hashed assets ``` Filenames are content-hashed so you can cache safely. `.br` files appear alongside JS/CSS when you pass `--compress`. ## Working with source maps Use `--sourcemap` when you need to debug production errors: ```bash theme={"system"} npx @linktr.ee/linkapp build --sourcemap ``` Keep source maps private; they expose your original source. ## Profiling and analysis ```bash theme={"system"} npx @linktr.ee/linkapp build --profile ``` The build completes normally and prints a reminder to analyze the output with your preferred bundle inspector. Combine with `--sourcemap` for more detail. ## When builds fail * **Missing modules or typos:** fix the import path or install the dependency. * **Static asset not found:** ensure files referenced from `public/` or `app/` exist. * **Large bundles:** rebuild with `--sourcemap` and inspect the emitted asset sizes to spot oversized dependencies. # linkapp deploy Source: https://docs.linktr.ee/cli/deploy Deploy your LinkApp to Linktree ## Usage ```bash theme={"system"} npx @linktr.ee/linkapp deploy [options] ``` Ship the contents of your LinkApp's `dist/` folder to Linktree. ## Options | Flag | Description | Default | | ---------------- | --------------------------------------------------------- | ------- | | `--qa` | Deploy to the QA environment instead of production | `false` | | `--skip-confirm` | Skip the deployment confirmation prompt | `false` | | `--force` | Force deployment even if the LinkApp is already published | `false` | | `-h, --help` | Show help | - | ## Quick start 1. Authenticate once per environment: ```bash theme={"system"} npx @linktr.ee/linkapp login # production npx @linktr.ee/linkapp login --qa # QA ``` 2. Optional: build locally if you want to inspect the output: ```bash theme={"system"} npx @linktr.ee/linkapp build ``` 3. Deploy: ```bash theme={"system"} npx @linktr.ee/linkapp deploy ``` ## What happens 1. **Initialize**: loads `linkapp.config.ts`, resolves your LinkApp ID, and checks for a valid auth token. 2. **Preflight**: builds if `dist/` is missing and runs validation checks. 3. **Prepare artifacts**: generates manifest files and packs uploadable assets. 4. **Confirm**: shows the file list and asks for confirmation (skip with `--skip-confirm`). 5. **Upload**: creates or updates the LinkApp on Linktree and prints success or error details. ## Requirements * `linkapp.config.ts` present in your project (in the root or `.config/`). * Logged in for the target environment (`linkapp login` or `linkapp login --qa`). * `dist/` folder exists, or let the command build it automatically. Use `--force` only when you intend to replace an already published build. # linkapp dev Source: https://docs.linktr.ee/cli/dev Start the development server with live preview and hot reloading ## Usage ```bash theme={"system"} npx @linktr.ee/linkapp dev [options] ``` Starts the Rsbuild dev server for your LinkApp. The CLI will try port `3000`; if it is busy, it picks the next available port and prints it. ## Options | Flag | Description | Default | | --------------- | ------------------------- | ------- | | `--port ` | Preferred dev server port | `3000` | | `-h, --help` | Show command help | - | ## What it does * Generates `.linkapp` entrypoints for layouts found in your project (`sheet`, `featured`, and `featured-carousel` when present). * Serves static assets from your project `public/` folder (mirrors legacy dev-server behavior). * Watches `linkapp.config.ts` (root or `.config/`) and restarts the dev server when it changes. * Prints ready URLs and the latest ready time after each start or restart. ## Keyboard shortcuts * `r` — restart the dev server * `u` — show the current URLs * `c` — clear the console * `q` or `Ctrl+C` — exit ## Typical flow ```bash theme={"system"} npx @linktr.ee/linkapp dev # edit app/, components/, linkapp.config.ts # browser reloads automatically ``` If the CLI restarts after a config change, it reloads preview props and settings from `linkapp.config.ts` before serving again. # linkapp login Source: https://docs.linktr.ee/cli/login Authenticate with Linktree ## Usage ```bash theme={"system"} npx @linktr.ee/linkapp login [options] ``` Log in with a browser-based device flow. The CLI stores a token locally for the chosen environment. ## Options | Flag | Description | Default | | ------------ | ------------------------------------------------- | ------- | | `--qa` | Login to the QA environment instead of production | `false` | | `-h, --help` | Show help | - | ## Flow 1. Checks if you're already logged in and asks whether to re-authenticate. 2. Starts the device authorization flow and shows a short code and URL. 3. Opens your browser so you can approve access (follow the URL if the browser doesn't open). 4. Polls until authorization completes, validates the token, and saves it locally with its expiry. ## Examples ```bash theme={"system"} npx @linktr.ee/linkapp login # Production npx @linktr.ee/linkapp login --qa # QA ``` If you need to capture the exact expiry, the CLI prints it after a successful login. # linkapp logout Source: https://docs.linktr.ee/cli/logout Sign out of Linktree ## Usage ```bash theme={"system"} npx @linktr.ee/linkapp logout [options] ``` Removes the stored token for the selected environment and opens a browser tab to clear the session. ## Options | Flag | Description | Default | | ------------ | ------------------------------------ | ------- | | `--qa` | Logout from QA instead of production | `false` | | `-h, --help` | Show help | - | ## Examples ```bash theme={"system"} npx @linktr.ee/linkapp logout # Production npx @linktr.ee/linkapp logout --qa # QA ``` If the browser does not open, use the printed logout URL to finish signing out. # linkapp test-url-match-rules Source: https://docs.linktr.ee/cli/test-url-match-rules Validate url_match_rules.json against a URL ## Usage ```bash theme={"system"} npx @linktr.ee/linkapp test-url-match-rules ``` Tests your local `url_match_rules.json` against the provided URL using the LinkApp API and prints the result payload. ## Options | Flag | Description | Default | | ------------------ | --------------------------------- | ------------------- | | `--qa` | Use the QA environment | `false` | | `--endpoint ` | Override the LinkApp API endpoint | derived from config | | `-h, --help` | Show command help | - | ## Prerequisites * Logged in (uses your stored production token). * `url_match_rules.json` present in the current working directory with valid JSON. ## What happens 1. Loads LinkApp API configuration (production by default) and your saved token. 2. Reads and parses `url_match_rules.json`. 3. Calls the LinkApp API to evaluate the provided URL against your rules. 4. Prints whether the match succeeded and the API response JSON. ## Example ```bash theme={"system"} npx @linktr.ee/linkapp test-url-match-rules https://example.com/path ``` # Configuration Source: https://docs.linktr.ee/essentials/configuration Configure your LinkApp with linkapp.config.ts ## File location Place `linkapp.config.ts` in your project root or `.config/linkapp.config.ts`. The CLI loads either path for `linkapp dev`, `linkapp build`, and `linkapp deploy`. ## Shape at a glance ```ts linkapp.config.ts theme={"system"} import type { LinkAppConfig } from "@linktr.ee/linkapp/types"; export default { manifest: { name: "My LinkApp", // -> ID: my-linkapp (cannot change after first deploy) tagline: "Do more with Linktree", description: ["Short summary"], manifest_version: "1.0.0", version: "0.1.0", category: "share", search_terms: ["demo"], supporting_links: { terms_of_service: "https://example.com/terms", privacy_policy: "https://example.com/privacy", }, author: { name: "You", accounts: ["your-linktree"], contact: { url: "https://linktr.ee/you", email: "hi@example.com" }, }, }, settings: { title: "My LinkApp", overview: { description: "Configure your app" }, elements: [ { id: "ctaText", inputType: "text", label: "CTA text", defaultValue: "Click me" }, ], }, url_match_rules: { hostnames: ["example.com"], patterns: [{ pathname: "/:slug" }], }, preview_props: { __linkUrl: "https://example.com", ctaText: "Preview CTA", }, } satisfies LinkAppConfig; ``` Top-level fields use `snake_case` to match the Linktree API; element properties use `camelCase`. ## Key points * **LinkApp ID**: derived from `manifest.name` on first deploy and remains fixed. Choose carefully before shipping. * **Settings → props**: every element in `settings.elements` becomes a prop in your layouts. Type them with `AppProps`: ```tsx app/expanded.tsx theme={"system"} import type { AppProps } from "@linktr.ee/linkapp"; type Settings = { ctaText: string }; export default function Sheet(props: AppProps) { return ; } ``` * **Preview props**: values under `preview_props` are injected during `linkapp dev` so you can see realistic data without publishing. * **URL match rules**: optional. Provide `hostnames` and `patterns` so Linktree can suggest your LinkApp when users paste matching URLs. ## Validation Configuration is validated during `linkapp build` and `linkapp deploy`. Fix reported errors (missing required fields, invalid patterns, etc.) before retrying. ## Next steps All manifest fields Build your settings UI # Manifest Reference Source: https://docs.linktr.ee/essentials/configuration/manifest Complete reference for LinkApp manifest configuration ## Overview The manifest describes how your LinkApp appears in the Linktree marketplace. Keep the copy clear, benefit-led, and easy for users to scan. ## Minimal Manifest Example ```ts linkapp.config.ts theme={"system"} export default { manifest: { name: "Weather LinkApp", tagline: "Show live weather on your profile", description: [ "Display current conditions right on your Linktree, with automatic hourly updates.", ], manifest_version: "1.0.0", version: "0.1.0", category: "share", search_terms: ["weather", "forecast"], supporting_links: { terms_of_service: "https://weather.com/terms", privacy_policy: "https://weather.com/privacy", }, author: { name: "Weather Co", accounts: ["weather-demo"], contact: { url: "https://linktr.ee/weather", email: "support@weather.com", }, }, }, }; ``` ## Field Reference | Field | Type | Required | Quick guidance | | ------------------ | -------------------------------------- | -------- | -------------------------------------------------------------------------- | | `name` | string (3-50 chars) | ✓ | Human-readable app name in title case (kebab-cased to generate LinkApp ID) | | `tagline` | string (3-200 chars) | ✓ | Short benefit statement for marketplace cards | | `description` | string\[] | ✓ | One or more paragraphs explaining value, features, and audience | | `manifest_version` | string (semver) | ✓ | Currently always `"1.0.0"` | | `version` | string (semver) | ✓ | Your app release version (follow semantic versioning) | | `category` | `grow` \| `sell` \| `share` \| `other` | ✓ | Choose the primary outcome your app delivers | | `search_terms` | string\[] (1-10) | ✓ | Keywords users might search (avoid duplicates) | | `supporting_links` | object | ✓ | Provide ToS & privacy URLs; docs/site optional | | `author` | object | ✓ | List who built the app and how to reach you | ## Supporting Links All URLs must be publicly accessible HTTPS links. ```ts theme={"system"} supporting_links: { terms_of_service: "https://example.com/terms", privacy_policy: "https://example.com/privacy", documentation: "https://example.com/docs", // optional website: "https://example.com" // optional } ``` ## Author Details Use real contact information so testers and customers can reach you. Only accounts listed below can access the app while it is in draft. ```ts theme={"system"} author: { name: "Acme Apps", accounts: ["acme-demo", "acme-staging"], contact: { url: "https://linktr.ee/acme", email: "support@acme.com" } } ``` ## LinkApp ID Generation **Important**: The LinkApp ID is automatically derived from the `name` field by converting it to kebab-case: * `"Weather LinkApp"` → `weather-linkapp` * `"Maps"` → `maps` * `"Bands In Town"` → `bands-in-town` This ID is used throughout the Linktree platform and **cannot be changed** after deployment. Choose your name carefully. ## Versioning & Validation * Bump `version` for every release using semantic versioning (`major.minor.patch`). * Run `linkapp build` to validate the manifest locally before deploying. * Fix common errors such as missing required fields, non-semver versions, or invalid URLs/emails. ## Next Steps Configure user-facing settings Auto-suggest your app for URLs Control how users find your app Return to configuration overview # Settings Configuration Source: https://docs.linktr.ee/essentials/configuration/settings Build user-facing settings forms for your LinkApp The `settings` section of `linkapp.config.ts` defines the form people fill out when they add your LinkApp. Whatever they enter comes back to your layouts as strongly typed props. ## Minimal Example ```ts linkapp.config.ts theme={"system"} export default { settings: { title: "Weather LinkApp", overview: { title: "Weather LinkApp", description: "Show live weather for a chosen city.", }, elements: [ { id: "location", inputType: "text", label: "City", defaultValue: "San Francisco", validation: { required: true }, }, ], }, }; ``` ### Flow 1. You define elements in `linkapp.config.ts`. 2. Users complete the generated form inside Linktree. 3. Layout components receive the answers via `AppProps`. ```tsx app/expanded.tsx theme={"system"} type Settings = { location: string; }; export default function ClassicLayout({ location }: AppProps) { return
Weather for {location}
; } ``` ## Elements in a Nutshell Each object in `settings.elements` becomes a form field and a prop. Focus on these keys: * `id` – unique prop name (use camelCase). * `inputType` – field type (`text`, `switch`, `select`, etc.). * `label` / `title` – copy shown in the form. * `defaultValue` – starter value. * `validation` – optional rules. For the complete element catalog, head to `/essentials/settings-reference`. ## Helpful Options * `title` – heading shown above the form. * `overview` – short description block at the top. * `uses_url` – require the built-in `linkUrl` field. * `settings_tab_title` – rename the settings tab text. * `setup_instructions` – markdown-friendly setup tips for complex flows. You rarely need to set `has_settings`; it's inferred when `elements` is present. ## Tips * Pick descriptive IDs because they become prop names. * Give defaults so the preview works immediately. * Keep descriptions short and actionable. Ready for more input types? Read `/essentials/settings-reference` when you need the details. # URL Matching Source: https://docs.linktr.ee/essentials/configuration/url-matching Auto-suggest your LinkApp when users enter matching URLs URL match rules surface your LinkApp when creators paste URLs that look like the ones you specify. They improve discovery only—creators can still add your app manually for any other URL. ```ts linkapp.config.ts theme={"system"} export default { url_match_rules: { hostnames: ["youtube.com", "youtu.be"], patterns: [ { pathname: "/watch", search: "v=:videoId" }, { pathname: "/:videoId" } ] } } ``` ## Define match rules ```ts theme={"system"} url_match_rules: { hostnames: string[], patterns?: Pattern[], notPatterns?: Pattern[] } ``` * `hostnames` (required) — domains your app supports. List every variant you expect (`youtube.com`, `www.youtube.com`, `m.youtube.com`, etc.). * `patterns` — optional pathname/search combos that must match. Leave empty to match any path on the hostnames. * `notPatterns` — optional exclusions, useful when matching a broad path with specific carve-outs. ```ts theme={"system"} url_match_rules: { hostnames: ["example.com"], patterns: [{ pathname: "/products/*" }], notPatterns: [{ pathname: "/products/admin" }] } ``` ## Pattern syntax cheatsheet Patterns use [URLPattern](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) semantics. | Pattern | Matches | Tip | | --------------------------------------------------- | ---------------------------------- | -------------------------------- | | `{ pathname: "/watch" }` | `https://youtube.com/watch` | Exact path | | `{ pathname: "/:id" }` | `https://youtu.be/abc123` | Named segment | | `{ pathname: "/products/*" }` | `https://example.com/products/a/b` | Wildcard suffix | | `{ search: "v=:videoId" }` | `?v=dQw4w9WgXcQ` | Include both key and placeholder | | `{ hostname: "m.youtube.com", pathname: "/watch" }` | `https://m.youtube.com/watch` | Override host per pattern | Paths are case-sensitive. Query parameters can appear in any order. ## Test your rules Use the CLI to confirm matches before shipping: ```bash theme={"system"} linkapp test-url-match-rules "https://youtube.com/watch?v=dQw4w9WgXcQ" ``` Positive matches print the LinkApp slug; failures show the part that missed. Add a script for quick re-testing: ```json package.json theme={"system"} { "scripts": { "test:url": "linkapp test-url-match-rules" } } ``` ## Troubleshoot fast * Double-check hostnames—include `www`/mobile/regional variants explicitly. * Ensure named parameters keep the colon (`/user/:id`, not `/user/id`). * Declare query parameters with both the key and placeholder (`search: "v=:videoId"`). * Replace overly broad wildcards if you see unexpected matches. ## Best practices * Start specific, then add broader fallbacks (`/watch` before `/:id`). * Document uncommon paths with inline comments for future maintainers. * Keep the total number of patterns below the 50-pattern limit. * Re-run `linkapp test-url-match-rules` after every config change. # Visibility & Discovery Source: https://docs.linktr.ee/essentials/configuration/visibility Control how users find and access your LinkApp `linkapp.config.ts` decides who can see your LinkApp and how they discover it. Focus on four areas: `manifest.author.accounts`, `manifest.search_terms`, `manifest.category`, and `url_match_rules`. ## Visibility States * **Draft** – Only the accounts listed in `manifest.author.accounts` can find or install the app. Add every teammate (and yourself) so you can test. ```ts theme={"system"} manifest: { author: { accounts: ["your-username", "qa-account"] } } ``` * **Published** – Available to everyone on Linktree. Publishing happens in the Linktree admin after review; the CLI always uploads drafts. ## Discovery Basics * **URL matching** – Provide hostnames and patterns so pasting a matching URL suggests your app. ```ts theme={"system"} url_match_rules: { hostnames: ["youtube.com", "youtu.be"], patterns: [{ pathname: "/watch", search: "v=:videoId" }] } ``` * **Keyword search** – Add up to 10 specific `manifest.search_terms` so admins can find you. * **Marketplace browse** – Choose the most relevant `manifest.category` (`grow`, `sell`, `share`, or `other`) to control where the app appears once published. * **Direct link** – Share `https://linktr.ee/admin?action=create-link&linkType=` to open the admin with your app preselected. Works for draft and published apps, but only whitelisted accounts can install a draft. ## Draft Testing Checklist 1. Add every tester to `manifest.author.accounts`. 2. Run `npx @linktr.ee/linkapp deploy` to upload the draft. 3. Sign in to Linktree with a whitelisted account and confirm you can: * Trigger URL suggestions with a matching link * Find the app via keyword search * Install from the direct link ## Going Public 1. Ship and test as a draft until it behaves exactly how you expect. 2. Request publishing through the Linktree admin or support team. 3. After approval the status switches to **Published**, the app appears in the marketplace, and all Linktree users can discover it. # Deployment Source: https://docs.linktr.ee/essentials/deployment Deploy your LinkApp to Linktree ## Prerequisites Before deploying, make sure you have: 1. A valid `linkapp.config.ts` with required `manifest` fields 2. A production build in `dist/` (or linkapp will build automatically) 3. Authentication credentials (run `npx @linktr.ee/linkapp login` first) ## Authenticate Log in to Linktree before your first deployment: ```bash Production theme={"system"} npx @linktr.ee/linkapp login ``` ```bash QA theme={"system"} npx @linktr.ee/linkapp login --qa ``` This opens a browser window for OAuth device flow authentication. Your tokens are stored in `~/.config/linkapp/auth-token.json`. ## Deploy your LinkApp Deploy to production: ```bash npm theme={"system"} npm run deploy ``` ```bash pnpm theme={"system"} pnpm deploy ``` ```bash yarn theme={"system"} yarn deploy ``` ```bash bun theme={"system"} bun deploy ``` Or use the CLI directly: ```bash theme={"system"} npx @linktr.ee/linkapp deploy ``` ### Deploy to QA To deploy to the QA environment: ```bash theme={"system"} npx @linktr.ee/linkapp deploy --qa ``` ## Deployment process When you run `linkapp deploy`: 1. **Validates** your project and config 2. **Builds** the app (if needed) 3. **Uploads** to Linktree ## Deployment flags | Flag | Description | | --------------- | ---------------------------------------------- | | `--qa` | Deploy to QA environment instead of production | | `--skip-build` | Skip building (use existing `dist/`) | | `--skip-checks` | Skip pre-deployment validation | | `--force` | Force deploy even if warnings | Example: ```bash theme={"system"} npx @linktr.ee/linkapp deploy --qa --skip-build ``` ## After deployment After a successful deployment, you'll see: ``` ✓ Deployed successfully! Build URL: https://linktr.ee/builds/abc123 Test link: https://linktr.ee/test?app=my-app Next steps: • View your build in the Linktree dashboard • Test your LinkApp on a Linktree profile • Share the build URL with your team ``` Use the test link to preview your LinkApp on a real Linktree profile before publishing. ## Troubleshooting ### Validation errors If you see config validation errors: ``` Error: Invalid config - manifest.title: Required - manifest.description: Max 200 characters - settings[0].id: Must be alphanumeric ``` Fix these in your `linkapp.config.ts` and redeploy. ### Bundle size warnings If your bundle exceeds 5MB: ``` ⚠ Warning: Bundle size is 6.2MB (exceeds 5MB recommended limit) ``` Consider using dynamic imports, removing unused dependencies, or optimizing images. ### Authentication errors If you see `401 Unauthorized`: ```bash theme={"system"} npx @linktr.ee/linkapp logout npx @linktr.ee/linkapp login ``` ### Build errors If the build fails during deployment: ```bash theme={"system"} # Build locally to see errors npx @linktr.ee/linkapp build # Fix errors, then deploy npx @linktr.ee/linkapp deploy --skip-build ``` ### Config schema errors Check your config against the schema: ```bash theme={"system"} # This will validate during build npx @linktr.ee/linkapp build ``` ### Deployment fails Enable verbose logging: ```bash theme={"system"} DEBUG=linkapp:* npx @linktr.ee/linkapp deploy ``` ## Multiple environments You can maintain separate LinkApps for development and production: ```ts linkapp.config.ts theme={"system"} export default { manifest: { name: process.env.LINKAPP_ENV === "qa" ? "My App QA" : "My App", // Generates IDs: "my-app-qa" or "my-app" }, settings: { title: process.env.LINKAPP_ENV === "qa" ? "My App (QA)" : "My App", }, }; ``` Deploy to QA: ```bash theme={"system"} LINKAPP_ENV=qa npx @linktr.ee/linkapp deploy --qa ``` Deploy to production: ```bash theme={"system"} LINKAPP_ENV=prod npx @linktr.ee/linkapp deploy ``` ## CI/CD integration Deploy from CI/CD pipelines: ```yaml .github/workflows/deploy.yml theme={"system"} name: Deploy LinkApp on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 18 - run: npm install - run: npm run build - run: npm run deploy env: LINKTREE_TOKEN: ${{ secrets.LINKTREE_TOKEN }} ``` Store your authentication token as a secret. Never commit `auth-token.json` or tokens to version control. ## Next steps See all deploy command options Learn about the build process # Layouts Source: https://docs.linktr.ee/essentials/layouts Learn how LinkApp layouts work and how to create them ## Required files Layouts live in your project `app/` folder. The CLI auto-detects these files: * `app/expanded.tsx` (required) — default layout for standard positions * `app/featured.tsx` (optional) — used for featured placement * `app/featured-carousel.tsx` (optional) — used when the featured layout is set to carousel * `app/layout.tsx` (optional) — wraps all other layouts * `app/globals.css` (recommended) — shared styles `linkapp dev`, `linkapp build`, and `linkapp deploy` validate that `app/expanded.tsx` exists and will include other layouts when the files are present. Legacy projects using `app/sheet.tsx` continue to work, but new projects should rename to `expanded.tsx`. ## Root layout wrapper (optional) `app/layout.tsx` runs once and wraps whichever layout is displayed: ```tsx app/layout.tsx theme={"system"} export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ## Expanded layout (required) ```tsx app/expanded.tsx theme={"system"} import type { AppProps } from "@linktr.ee/linkapp"; type Settings = { ctaText: string }; export default function Expanded({ linkUrl, theme, ctaText }: AppProps) { return ( {ctaText} ); } ``` ## Featured layout (optional) ```tsx app/featured.tsx theme={"system"} import type { AppProps } from "@linktr.ee/linkapp"; export default function Featured({ linkUrl, theme }: AppProps) { return (
View details
); } ``` ## Featured carousel (optional) Provide `app/featured-carousel.tsx` to support carousel group layouts: ```tsx app/featured-carousel.tsx theme={"system"} import type { AppProps } from "@linktr.ee/linkapp"; export default function FeaturedCarousel({ linkUrl }: AppProps) { return
Explore: {linkUrl}
; } ``` When Linktree requests a featured carousel, the CLI serves `featured-carousel.tsx` if it exists; otherwise it falls back to `featured.tsx`. ## Common props All layouts receive: * Preview props you set in `linkapp.config.ts` (`preview_props`) * User settings defined in `settings.elements` * Linktree context such as `linkUrl`, `theme`, `layout`/`__layout`, and `groupLayoutOption` (used to select featured vs featured-carousel) Type them with `AppProps` to get autocomplete and type safety. ## Next steps Define settings and preview props Run local preview and test layouts # Settings Element Reference Source: https://docs.linktr.ee/essentials/settings-reference Complete reference for all LinkApp settings element types ## Overview Settings elements define the form fields shown to users when they configure your LinkApp. Each element type provides different input options—from simple text boxes to complex arrays. ```ts linkapp.config.ts theme={"system"} export default { settings: { elements: [ { id: "username", // Becomes prop name inputType: "text", // Element type title: "Username", // Field title defaultValue: "", // Initial value }, ], }, }; ``` The `id` property becomes a prop in your layout component: ```tsx app/expanded.tsx theme={"system"} type Settings = { username: string }; export default function ClassicLayout({ username }: AppProps) { return
Hello, {username}!
; } ``` ## Quick Reference | Element Type | Description | Use Case | Details | | -------------- | ------------------------- | ------------------------------- | ----------------------------------------------- | | `text` | Single-line text input | Names, titles, short text | [→](/essentials/settings/text-inputs#text) | | `textarea` | Multi-line text input | Descriptions, long text | [→](/essentials/settings/text-inputs#textarea) | | `url` | URL input with validation | Website links | [→](/essentials/settings/text-inputs#url) | | `number` | Number input | Counts, limits, quantities | [→](/essentials/settings/text-inputs#number) | | `select` | Dropdown menu | Predefined choices (3+ options) | [→](/essentials/settings/selections#select) | | `radio` | Radio button group | Exclusive choices (2-5 options) | [→](/essentials/settings/selections#radio) | | `checkbox` | Checkbox input | Multiple selections, acceptance | [→](/essentials/settings/selections#checkbox) | | `switch` | Boolean toggle | Enable/disable features | [→](/essentials/settings/toggles#switch) | | `linkBehavior` | Linktree link behavior | Embed vs. direct link | [→](/essentials/settings/toggles#link-behavior) | | `location` | Google Places picker | Addresses, venues, stores | [→](/essentials/settings/location) | | `array` | Dynamic list of items | Collections, lists | [→](/essentials/settings/advanced#array) | | `integration` | Third-party integration | Email marketing, etc. | [→](/essentials/settings/advanced#integration) | | `file` | File upload | Images, documents | [→](/essentials/settings/advanced#file-upload) | | `button` | Action button | Trigger actions | [→](/essentials/settings/advanced#button) | ## Common Properties All element types share these base properties: | Property | Type | Required | Description | | -------------------- | -------- | -------- | ------------------------------------------- | | `id` | `string` | Yes | Unique identifier (becomes prop name) | | `inputType` | `string` | Yes | Element type from the table above | | `title` | `string` | No | Field title displayed above input | | `description` | `string` | No | Help text explaining the field | | `defaultValue` | `any` | No | Initial value (type depends on inputType) | | `validation` | `object` | No | Validation rules (required, min, max, etc.) | | `conditionalDisplay` | `object` | No | Show/hide based on other elements | ### Example ```ts theme={"system"} { id: "apiKey", inputType: "text", title: "API Key", description: "Get your API key from example.com/settings", placeholder: "Enter your API key", defaultValue: "", validation: { required: true, minLength: 20 } } ``` ## Element Categories ### Text Inputs Simple and multi-line text entry fields. Single-line and multi-line text inputs Validated URL inputs for website links Numeric values with min/max validation See detailed documentation ### Selection Controls Dropdowns, radio buttons, and checkboxes for choosing options. Choose one from many options Visual option selection (2-5 choices) Single or multiple selections See detailed documentation ### Toggles & Switches Boolean controls for on/off states. Enable/disable features Control how links open (Linktree-specific) See detailed documentation ### Advanced Elements Complex inputs for lists, integrations, and conditional logic. Dynamic lists of structured items Connect third-party services Show/hide elements based on values See detailed documentation ## Validation Add validation rules to ensure correct input: ```ts theme={"system"} { id: "email", inputType: "text", validation: { required: true, // Must not be empty pattern: "^[^@]+@[^@]+$", // Must match regex minLength: 5, // Minimum characters maxLength: 100 // Maximum characters (shows real-time counter) } } ``` ### Validation Rules Reference | Rule | Applies To | Description | | ------------- | ------------------- | ---------------------------------------------- | | `required` | All types | Field must not be empty | | `minLength` | Text, textarea, url | Minimum character count | | `maxLength` | Text, textarea, url | Maximum character count (enables char counter) | | `pattern` | Text, url | Regular expression match | | `min` | Number | Minimum numeric value | | `max` | Number | Maximum numeric value | | `minItems` | Array | Minimum number of array items | | `maxItems` | Array | Maximum number of array items | | `maxFileSize` | File | Maximum file size in bytes | **Character Counter:** When you set `maxLength` on a text field, the Linktree editor automatically shows a real-time character counter (e.g., "45/100 characters") as users type. ### Array Validation Example ```ts theme={"system"} { id: "testimonials", inputType: "array", validation: { minItems: 1, // At least 1 item required maxItems: 10 // Maximum 10 items allowed }, array_options: { add_item_button_text: "Add testimonial", item_format: "{{name}} - {{title}}" }, array_elements: [ { id: "name", inputType: "text", title: "Name" }, { id: "title", inputType: "text", title: "Title" } ] } ``` ### File Validation Example ```ts theme={"system"} { id: "coverImage", inputType: "file", title: "Cover Image", accept: ["image/jpeg", "image/png"], validation: { required: true, maxFileSize: 5242880 // 5MB in bytes } } ``` See validation examples for each element type ## Type Safety Define settings types for full TypeScript support: ```ts theme={"system"} // Define settings type matching your config type MyLinkAppSettings = { showTitle: boolean // switch element username: string // text element theme: 'light' | 'dark' // select element itemCount: number // number element } // Use in layout with full type safety export default function ClassicLayout({ showTitle, username, theme, itemCount }: AppProps) { // TypeScript knows all prop types ✨ return
...
} ``` TypeScript will autocomplete prop names and catch type errors during development! ## Best Practices ### Use Descriptive IDs ```ts theme={"system"} // ✅ Good - clear, descriptive { id: "showTitle"; } { id: "backgroundColor"; } // ❌ Avoid - abbreviations, unclear { id: "st"; } { id: "bgClr"; } ``` ### Provide Default Values ```ts theme={"system"} { id: "itemCount", defaultValue: 10 // Sensible default } ``` ### Add Help Text ```ts theme={"system"} { id: "apiKey", description: "Get your API key from https://example.com/settings/api" } ``` ### Choose Appropriate Types ```ts theme={"system"} // ✅ Good - validates URL format { id: "website", inputType: "url" } // ❌ Less good - allows any text { id: "website", inputType: "text" } ``` ## Complete Example Here's a real-world example for a weather LinkApp: ```ts linkapp.config.ts theme={"system"} export default { settings: { title: "Weather", elements: [ { id: "location", inputType: "text", title: "Location", description: "City name for weather data", label: "City", defaultValue: "San Francisco", validation: { required: true }, }, { id: "units", inputType: "select", title: "Temperature Units", label: "Units", defaultValue: "celsius", options: [ { label: "Celsius (°C)", value: "celsius" }, { label: "Fahrenheit (°F)", value: "fahrenheit" }, ], }, { id: "showForecast", inputType: "switch", title: "Display Options", label: "Show 5-day forecast", defaultValue: true, }, ], }, }; ``` ## Explore Element Types Text, textarea, URL, and number inputs Select, radio, and checkbox elements Switch and link behavior elements Arrays, integrations, and conditional display ## Next Steps Learn about settings structure Use settings in your layouts See real-world examples # Advanced Elements Source: https://docs.linktr.ee/essentials/settings/advanced Arrays, integrations, conditional display, and specialized elements ## Overview Advanced elements provide complex functionality like dynamic lists, third-party integrations, and conditional forms. These elements unlock powerful features for sophisticated LinkApps. | Element | Use Case | | -------------------- | --------------------------------------------------------- | | `array` | Dynamic lists of structured items (FAQs, links, products) | | `integration` | Connect third-party services (email marketing, etc.) | | `conditionalDisplay` | Show/hide elements based on other values | | `file` | Upload images or documents | | `button` | Trigger actions or workflows | ## Array Dynamic array of structured inputs for collecting lists of items. Perfect for FAQs, social links, product lists, or any repeating data. ### Properties | Property | Type | Description | | ---------------- | --------- | ------------------------------------- | | `id` | `string` | Unique identifier (becomes prop name) | | `inputType` | `'array'` | Must be `'array'` | | `title` | `string` | Field title | | `description` | `string` | Help text | | `label` | `string` | Label for the array | | `defaultValue` | `array` | Initial array values | | `array_options` | `object` | Array UI configuration | | `array_elements` | `array` | Element definitions for each item | | `validation` | `object` | Validation rules | ### Array Options Configure the array UI in the admin: | Property | Type | Description | | ----------------------------- | -------- | --------------------------------------- | | `min` | `number` | Minimum items required | | `max` | `number` | Maximum items allowed | | `add_item_button_text` | `string` | "Add" button text (empty array) | | `add_second_item_button_text` | `string` | "Add" button text (after first item) | | `add_item_title` | `string` | Add dialog title | | `edit_item_title` | `string` | Edit dialog title | | `item_format` | `string` | Display template (e.g., `{{question}}`) | ### Validation | Rule | Type | Description | | ---------- | --------- | -------------------------------------------------- | | `required` | `boolean` | Array must have at least one item | | `maxSize` | `number` | Maximum items (alternative to `array_options.max`) | ### Example: Social Links ```ts linkapp.config.ts theme={"system"} { id: "socialLinks", inputType: "array", title: "Social Links", description: "Add your social media profiles", label: "Social profiles", array_options: { min: 1, max: 5, add_item_button_text: "Add social link", add_second_item_button_text: "Add another link", item_format: "{{platform}}: {{url}}" }, array_elements: [ { id: "platform", inputType: "select", title: "Platform", label: "Social platform", options: [ { label: "Twitter", value: "twitter" }, { label: "Instagram", value: "instagram" }, { label: "TikTok", value: "tiktok" }, { label: "LinkedIn", value: "linkedin" } ], validation: { required: true } }, { id: "url", inputType: "url", title: "Profile URL", label: "URL", placeholder: "https://twitter.com/username", validation: { required: true } } ], validation: { required: true } } ``` **Usage in layout:** ```tsx app/expanded.tsx theme={"system"} type SocialLink = { platform: 'twitter' | 'instagram' | 'tiktok' | 'linkedin' url: string } type Settings = { socialLinks: SocialLink[] } export default function ClassicLayout({ socialLinks }: AppProps) { return (

Follow me:

{socialLinks.map((link, index) => ( {link.platform} ))}
) } ``` ### Example: FAQ List ```ts linkapp.config.ts theme={"system"} { id: "questions_list", inputType: "array", title: "Questions", validation: { required: true, maxSize: 20 }, array_options: { add_item_button_text: "Add a question", add_item_title: "Add question", add_second_item_button_text: "Add another question", edit_item_title: "Edit question", item_format: "{{question}}" // Shows question text in list }, array_elements: [ { id: "question", title: "Question", inputType: "text", validation: { required: true, minLength: 1, maxLength: 200 } }, { id: "answer", title: "Answer", inputType: "textarea", validation: { required: true, minLength: 1, maxLength: 800 } } ] } ``` **Usage in layout:** ```tsx app/expanded.tsx theme={"system"} type Question = { question: string answer: string } type FAQSettings = { questions_list: Question[] } export default function ClassicLayout({ questions_list }: AppProps) { return (

Frequently Asked Questions

{questions_list.map((item, index) => (
{item.question}

{item.answer}

))}
) } ``` Use `item_format` with `{{fieldId}}` syntax to show a preview of each item in the admin list view. ## Conditional Display Show or hide elements based on the value of other elements. Creates dynamic forms that adapt to user choices. ### Properties | Property | Type | Description | | ----------- | --------------------------------------- | ----------------------------------------- | | `dependsOn` | `string` | ID of the element this depends on | | `value` | `string \| boolean` | Value that triggers display | | `operator` | `'equals' \| 'notEquals' \| 'contains'` | Comparison operator (default: `'equals'`) | Add `conditionalDisplay` to any element to control its visibility. ### Example: Conditional Email Input ```ts linkapp.config.ts theme={"system"} elements: [ { id: "enableNotifications", inputType: "switch", title: "Notifications", label: "Enable email notifications", defaultValue: false }, { id: "notificationEmail", inputType: "text", title: "Email Address", label: "Email", placeholder: "you@example.com", validation: { required: true }, // Only show if enableNotifications is true conditionalDisplay: { dependsOn: "enableNotifications", value: true, operator: "equals" } } ] ``` ### Example: Conditional Based on Select ```ts linkapp.config.ts theme={"system"} elements: [ { id: "integrationProvider", inputType: "select", title: "Integration Provider", options: [ { label: "Mailchimp", value: "mailchimp" }, { label: "SendGrid", value: "sendgrid" }, { label: "Custom API", value: "custom" } ] }, { id: "customApiEndpoint", inputType: "url", title: "Custom API Endpoint", placeholder: "https://api.example.com/notify", validation: { required: true }, // Only show if integrationProvider is 'custom' conditionalDisplay: { dependsOn: "integrationProvider", value: "custom" } }, { id: "customApiKey", inputType: "text", title: "API Key", placeholder: "Enter your API key", // Also only show for custom conditionalDisplay: { dependsOn: "integrationProvider", value: "custom" } } ] ``` ### Operators | Operator | Description | Example | | ----------- | --------------------- | ------------------------------------------------------ | | `equals` | Exact match (default) | `value: true` shows when field is true | | `notEquals` | Does not match | `value: "none"` shows when field is not "none" | | `contains` | Array contains value | `value: "advanced"` shows if array includes "advanced" | Use conditional display to reduce form complexity and show users only relevant fields. ## Integration Connect to third-party services like email marketing platforms. Requires users to have the integration already set up in their Linktree account. ### Properties | Property | Type | Description | | ------------- | --------------- | ------------------------------------- | | `id` | `string` | Unique identifier | | `inputType` | `'integration'` | Must be `'integration'` | | `capability` | `string` | Required integration capability | | `vendor` | `string` | Specific vendor identifier (optional) | | `title` | `string` | Field title | | `description` | `string` | Help text | ### Example ```ts linkapp.config.ts theme={"system"} { id: "emailIntegration", inputType: "integration", title: "Email Marketing", description: "Connect your email marketing service to collect subscribers", capability: "MANAGE_EMAIL_SUBSCRIBERS", vendor: "mailchimp" // Optional: specific vendor } ``` Users must have the integration already connected in their Linktree account. This element lets them select which integration to use with your LinkApp. ## File Upload Upload images, documents, or other file types. ### Properties | Property | Type | Description | | ------------- | ---------- | -------------------- | | `id` | `string` | Unique identifier | | `inputType` | `'file'` | Must be `'file'` | | `title` | `string` | Field title | | `description` | `string` | Help text | | `label` | `string` | Input label | | `accept` | `string[]` | Accepted file types | | `multiple` | `boolean` | Allow multiple files | | `validation` | `object` | Validation rules | ### Example ```ts linkapp.config.ts theme={"system"} { id: "bannerImage", inputType: "file", title: "Banner Image", description: "Upload a banner image for your LinkApp", label: "Choose image", accept: ["image/png", "image/jpeg", "image/webp"], multiple: false, validation: { required: true } } ``` ## Button Interactive button element that triggers actions. ### Properties | Property | Type | Description | | ----------- | ---------- | -------------------- | | `id` | `string` | Unique identifier | | `inputType` | `'button'` | Must be `'button'` | | `label` | `string` | Button text | | `action` | `object` | Action configuration | ### Action Properties | Property | Type | Description | | ---------------- | --------------- | ---------------------- | | `on` | `'click'` | Interaction event | | `type` | `'update-link'` | Action type | | `data.link_type` | `string` | Link type to update to | ### Example ```ts linkapp.config.ts theme={"system"} { id: "upgradeButton", inputType: "button", label: "Upgrade to Pro Version", action: { on: "click", type: "update-link", data: { link_type: "pro-version-app" } } } ``` ## Complete Example: Advanced FAQ App Here's a complete example using arrays and conditional display: ```ts linkapp.config.ts theme={"system"} export default { settings: { title: "FAQ Manager", icon: "question-mark", uses_url: false, elements: [ // Array of FAQ items { id: "questions_list", inputType: "array", validation: { required: true, maxSize: 20 }, array_options: { add_item_button_text: "Add a question", add_item_title: "Add question", item_format: "{{question}}" }, array_elements: [ { id: "question", title: "Question", inputType: "text", validation: { required: true, maxLength: 200 } }, { id: "answer", title: "Answer", inputType: "textarea", validation: { required: true, maxLength: 800 } } ] }, // Enable search toggle { id: "enableSearch", inputType: "switch", title: "Search Options", label: "Enable question search", defaultValue: false }, // Search placeholder (conditional) { id: "searchPlaceholder", inputType: "text", title: "Search Placeholder", label: "Placeholder text", defaultValue: "Search questions...", conditionalDisplay: { dependsOn: "enableSearch", value: true } }, // Enable categories toggle { id: "enableCategories", inputType: "switch", title: "Organization", label: "Group questions by category", defaultValue: false }, // Category list (conditional) { id: "categories", inputType: "array", title: "Categories", description: "Define question categories", conditionalDisplay: { dependsOn: "enableCategories", value: true }, array_options: { add_item_button_text: "Add category", item_format: "{{name}}" }, array_elements: [ { id: "name", inputType: "text", title: "Category Name", validation: { required: true, maxLength: 50 } } ] } ] } } ``` ## Validation Strategies ### Required Arrays ```ts theme={"system"} { id: "items", inputType: "array", validation: { required: true // At least one item required } } ``` ### Array Size Limits ```ts theme={"system"} { id: "items", inputType: "array", array_options: { min: 1, // Minimum items max: 10 // Maximum items } } ``` Or use `validation.maxSize`: ```ts theme={"system"} { id: "items", inputType: "array", validation: { maxSize: 10 } } ``` ### Nested Validation Array elements can have their own validation: ```ts theme={"system"} array_elements: [ { id: "email", inputType: "text", validation: { required: true, pattern: "^[^@]+@[^@]+\\.[^@]+$" // Email format } } ] ``` ## Best Practices ### Array Design ```ts theme={"system"} // ✅ Good - clear item format array_options: { item_format: "{{question}}" // Shows question text } // ✅ Good - descriptive button text array_options: { add_item_button_text: "Add a social link", add_second_item_button_text: "Add another social link" } ``` ### Conditional Display ```ts theme={"system"} // ✅ Good - progressive disclosure { id: "advancedMode", inputType: "switch", label: "Show advanced options" }, { id: "advancedSettings", inputType: "text", conditionalDisplay: { dependsOn: "advancedMode", value: true } } ``` ### Array Element Names ```ts theme={"system"} // ✅ Good - descriptive IDs array_elements: [ { id: "question", inputType: "text" }, { id: "answer", inputType: "textarea" } ] // ❌ Avoid - unclear array_elements: [ { id: "q", inputType: "text" }, { id: "a", inputType: "textarea" } ] ``` ## Type Safety Define array item types: ```ts theme={"system"} type Question = { question: string answer: string } type SocialLink = { platform: string url: string } type Settings = { questions_list: Question[] socialLinks: SocialLink[] enableSearch: boolean searchPlaceholder: string // Only shown if enableSearch is true } export default function ClassicLayout(props: AppProps) { const { questions_list, socialLinks, enableSearch, searchPlaceholder } = props return
...
} ``` ## Next Steps Text, textarea, URL, and number inputs Select, radio, and checkbox elements Switch and link behavior elements Return to settings reference overview # Location Element Source: https://docs.linktr.ee/essentials/settings/location Google Places location picker for collecting place data ## Overview The location element provides a Google Places search interface for users to select a location. It stores the full place data including coordinates, name, address, and optional metadata like ratings and photos. | Element | Best For | Value Type | | ---------- | ----------------------------- | ------------ | | `location` | Store addresses, venues, POIs | `PlaceValue` | ## Properties | Property | Type | Description | | -------------- | ------------ | ------------------------------------- | | `id` | `string` | Unique identifier (becomes prop name) | | `inputType` | `'location'` | Must be `'location'` | | `title` | `string` | Field title | | `description` | `string` | Help text | | `placeholder` | `string` | Search input placeholder text | | `defaultValue` | `PlaceValue` | Initial selected location | | `validation` | `object` | Validation rules | ### Validation | Rule | Type | Description | | ---------- | --------- | --------------------------- | | `required` | `boolean` | A location must be selected | ### PlaceValue Structure The selected location value has this structure: ```ts theme={"system"} interface PlaceValue { placeId: string // Google Places ID name: string // Place name address: string // Formatted address lat: number // Latitude lng: number // Longitude photos?: string[] // Photo URLs (optional) rating?: number // Place rating 1-5 (optional) userRatingsTotal?: number // Number of reviews (optional) types?: string[] // Place types (optional) website?: string // Website URL (optional) phoneNumber?: string // Phone number (optional) } ``` ## Example ### Basic Location ```ts linkapp.config.ts theme={"system"} { id: "storeLocation", inputType: "location", title: "Store Location", description: "Select your store location", validation: { required: true } } ``` ### With Custom Placeholder ```ts linkapp.config.ts theme={"system"} { id: "venue", inputType: "location", title: "Event Venue", description: "Search for the venue", placeholder: "Search for a venue...", validation: { required: true } } ``` ## Usage in Layout ```tsx app/expanded.tsx theme={"system"} import type { AppProps, PlaceValue } from "@linktr.ee/linkapp/types" type Settings = { storeLocation: PlaceValue } export default function ClassicLayout({ storeLocation }: AppProps) { return (

{storeLocation.name}

{storeLocation.address}

{storeLocation.rating && (

Rating: {storeLocation.rating}/5 ({storeLocation.userRatingsTotal} reviews)

)} View on Google Maps
) } ``` ## Using with Arrays Location elements work inside arrays for collecting multiple locations: ```ts linkapp.config.ts theme={"system"} { id: "locations", inputType: "array", title: "Store Locations", description: "Add your store locations", array_options: { min: 1, max: 10, add_item_button_text: "Add location", add_second_item_button_text: "Add another location", edit_item_title: "Edit location", add_item_title: "Add location", item_format: "{{name}}" }, array_elements: [ { id: "name", inputType: "text", title: "Location Name", placeholder: "e.g., Downtown Store", validation: { required: true, maxLength: 50 } }, { id: "place", inputType: "location", title: "Address", description: "Search for the store address", validation: { required: true } } ] } ``` **Usage in Layout:** ```tsx app/expanded.tsx theme={"system"} import type { AppProps, PlaceValue } from "@linktr.ee/linkapp/types" type StoreLocation = { name: string place: PlaceValue } type Settings = { locations: StoreLocation[] } export default function ClassicLayout({ locations }: AppProps) { return (
{locations.map((location, index) => (

{location.name}

{location.place.address}

{location.place.rating && ( Rating: {location.place.rating}/5 )}
))}
) } ``` ## Common Patterns **Business location:** ```ts theme={"system"} { id: "businessAddress", inputType: "location", title: "Business Address", description: "Your business location for customers to find you", placeholder: "Search for your business...", validation: { required: true } } ``` **Event venue:** ```ts theme={"system"} { id: "venue", inputType: "location", title: "Event Venue", description: "Where the event will be held", placeholder: "Search for a venue...", validation: { required: true } } ``` The actual Google Places picker is rendered in the Linktree admin (frontyard). The dev server shows a preview placeholder. ## Best Practices 1. **Provide clear descriptions** explaining what type of location is expected 2. **Use custom placeholders** to guide users on what to search for 3. **Consider using arrays** when users need to add multiple locations 4. **Handle optional fields** - rating, photos, etc. may not be available for all places ## Next Steps Text, textarea, URL, and number inputs Arrays, integrations, and conditional display Select, radio, and checkbox elements Return to settings reference overview # Selection Elements Source: https://docs.linktr.ee/essentials/settings/selections Select, radio, and checkbox elements for choosing options ## Overview Selection elements let users choose from predefined options. Pick the right element based on how many options you have and whether users can select one or multiple items. | Element | Options | Selection | Best For | | ---------- | ------- | ------------------ | --------------------------------------- | | `select` | 3+ | Single | Many options, dropdown UI | | `radio` | 2-5 | Single | Few options, visible comparison | | `checkbox` | 1+ | Single or multiple | Multiple selections or terms acceptance | ## Select Dropdown Dropdown menu for choosing one option from a list. Best when you have many options (3+). ### Properties | Property | Type | Description | | -------------- | ------------------ | ------------------------------------- | | `id` | `string` | Unique identifier (becomes prop name) | | `inputType` | `'select'` | Must be `'select'` | | `title` | `string` | Field title | | `description` | `string` | Help text | | `label` | `string` | Input label | | `defaultValue` | `string \| number` | Initial selected value | | `options` | `array` | Array of `{ label, value }` objects | | `validation` | `object` | Validation rules | ### Options Format ```ts theme={"system"} options: [ { label: "Display text shown to user", value: "value-sent-to-layout" }, { label: "Light Mode", value: "light" }, { label: "Dark Mode", value: "dark" }, ]; ``` ### Validation | Rule | Type | Description | | ---------- | --------- | ------------------------ | | `required` | `boolean` | A value must be selected | ### Example ```ts linkapp.config.ts theme={"system"} { id: "theme", inputType: "select", title: "Theme", description: "Choose a color theme", label: "Color theme", defaultValue: "light", options: [ { label: "Light Mode", value: "light" }, { label: "Dark Mode", value: "dark" }, { label: "Auto (System)", value: "auto" } ], validation: { required: true } } ``` ### Usage in Layout ```tsx app/expanded.tsx theme={"system"} type Settings = { theme: "light" | "dark" | "auto"; }; export default function ClassicLayout({ theme }: AppProps) { const isDark = theme === "dark" || (theme === "auto" && window.matchMedia("(prefers-color-scheme: dark)").matches); return
Content
; } ``` ### Common Patterns **Theme selector:** ```ts theme={"system"} { id: "colorScheme", inputType: "select", title: "Color Scheme", label: "Theme", defaultValue: "auto", options: [ { label: "Light", value: "light" }, { label: "Dark", value: "dark" }, { label: "Auto", value: "auto" } ] } ``` **Category/type selector:** ```ts theme={"system"} { id: "contentType", inputType: "select", title: "Content Type", label: "Type", defaultValue: "article", options: [ { label: "Article", value: "article" }, { label: "Video", value: "video" }, { label: "Gallery", value: "gallery" }, { label: "Product", value: "product" } ], validation: { required: true } } ``` **Language selector:** ```ts theme={"system"} { id: "language", inputType: "select", title: "Language", label: "Display language", defaultValue: "en", options: [ { label: "English", value: "en" }, { label: "Spanish", value: "es" }, { label: "French", value: "fr" }, { label: "German", value: "de" } ] } ``` **Units/format:** ```ts theme={"system"} { id: "dateFormat", inputType: "select", title: "Date Format", label: "Format", defaultValue: "mdy", options: [ { label: "MM/DD/YYYY", value: "mdy" }, { label: "DD/MM/YYYY", value: "dmy" }, { label: "YYYY-MM-DD", value: "ymd" } ] } ``` Use `select` when you have 3+ options. For 2-5 options, consider `radio` for better visual comparison. ## Radio Buttons Radio button group for choosing one option. Best for 2-5 options where users benefit from seeing all choices at once. ### Properties | Property | Type | Description | | -------------- | --------- | ----------------------------------- | | `id` | `string` | Unique identifier | | `inputType` | `'radio'` | Must be `'radio'` | | `title` | `string` | Field title | | `description` | `string` | Help text | | `options` | `array` | Array of `{ label, value }` objects | | `defaultValue` | `string` | Initial selected value | | `validation` | `object` | Validation rules | ### Validation | Rule | Type | Description | | ---------- | --------- | ------------------------ | | `required` | `boolean` | A value must be selected | ### Example ```ts linkapp.config.ts theme={"system"} { id: "displayMode", inputType: "radio", title: "Display Mode", description: "Choose how to display content", options: [ { label: "List View", value: "list" }, { label: "Grid View", value: "grid" }, { label: "Compact", value: "compact" } ], defaultValue: "list", validation: { required: true } } ``` ### Usage in Layout ```tsx app/expanded.tsx theme={"system"} type Settings = { displayMode: "list" | "grid" | "compact"; }; export default function ClassicLayout({ displayMode }: AppProps) { return (
{displayMode === "grid" ? : }
); } ``` ### Common Patterns **Layout choice:** ```ts theme={"system"} { id: "layout", inputType: "radio", title: "Layout Style", options: [ { label: "Single Column", value: "single" }, { label: "Two Columns", value: "two" }, { label: "Three Columns", value: "three" } ], defaultValue: "single" } ``` **Size selector:** ```ts theme={"system"} { id: "size", inputType: "radio", title: "LinkApp Size", options: [ { label: "Small", value: "sm" }, { label: "Medium", value: "md" }, { label: "Large", value: "lg" } ], defaultValue: "md" } ``` **Alignment:** ```ts theme={"system"} { id: "alignment", inputType: "radio", title: "Text Alignment", options: [ { label: "Left", value: "left" }, { label: "Center", value: "center" }, { label: "Right", value: "right" } ], defaultValue: "left" } ``` Radio buttons show all options at once, making them perfect for choices users need to compare visually. Use `select` if you have more than 5 options. ## Checkbox Checkbox input for single acceptance (like terms) or multiple selections. ### Properties | Property | Type | Description | | -------------- | -------------------- | ----------------------------------- | | `id` | `string` | Unique identifier | | `inputType` | `'checkbox'` | Must be `'checkbox'` | | `title` | `string` | Field title | | `description` | `string` | Help text | | `label` | `string` | Label for checkbox/group | | `options` | `array` | Array of `{ label, value }` objects | | `defaultValue` | `string \| string[]` | Initial selected value(s) | | `validation` | `object` | Validation rules | ### Validation | Rule | Type | Description | | ---------- | --------- | ---------------------------- | | `required` | `boolean` | At least one must be checked | * **Single checkbox**: Provide 1 option (returns array with 1 item or empty array) - **Multiple checkboxes**: Provide 2+ options (returns array of selected values) ### Example: Single Checkbox For terms acceptance or single on/off choice: ```ts linkapp.config.ts theme={"system"} { id: "termsAccepted", inputType: "checkbox", title: "Terms and Conditions", description: "You must accept to continue", options: [ { label: "I agree to the terms and conditions", value: "accepted" } ], validation: { required: true } } ``` **Usage:** ```tsx app/expanded.tsx theme={"system"} type Settings = { termsAccepted: string[]; // ['accepted'] or [] }; export default function ClassicLayout({ termsAccepted }: AppProps) { const hasAccepted = termsAccepted.includes("accepted"); if (!hasAccepted) { return
Please accept terms to continue
; } return
Welcome!
; } ``` ### Example: Multiple Checkboxes For selecting multiple options: ```ts linkapp.config.ts theme={"system"} { id: "features", inputType: "checkbox", title: "Features", description: "Select features to enable", label: "Enabled features", defaultValue: ["notifications"], options: [ { label: "Email Notifications", value: "notifications" }, { label: "Dark Mode", value: "darkMode" }, { label: "Analytics", value: "analytics" }, { label: "Beta Features", value: "beta" } ] } ``` **Usage:** ```tsx app/expanded.tsx theme={"system"} type Settings = { features: string[]; // e.g., ['notifications', 'analytics'] }; export default function ClassicLayout({ features }: AppProps) { const hasNotifications = features.includes("notifications"); const hasDarkMode = features.includes("darkMode"); const hasAnalytics = features.includes("analytics"); return (
{hasNotifications && } {hasAnalytics && }
); } ``` ### Common Patterns **Terms acceptance:** ```ts theme={"system"} { id: "terms", inputType: "checkbox", title: "Agreement", options: [ { label: "I agree to the Terms of Service", value: "tos" } ], validation: { required: true } } ``` **Optional features:** ```ts theme={"system"} { id: "optionalFeatures", inputType: "checkbox", title: "Optional Features", description: "Choose which features to enable", defaultValue: [], options: [ { label: "Show timestamps", value: "timestamps" }, { label: "Enable comments", value: "comments" }, { label: "Display author info", value: "authorInfo" } ] } ``` **Content filters:** ```ts theme={"system"} { id: "contentTypes", inputType: "checkbox", title: "Content Types", description: "Select types to display", defaultValue: ["articles", "videos"], options: [ { label: "Articles", value: "articles" }, { label: "Videos", value: "videos" }, { label: "Podcasts", value: "podcasts" }, { label: "Images", value: "images" } ], validation: { required: true } // At least one must be selected } ``` ## Comparison Choose the right selection element: | Scenario | Use | | -------------------------------------------------- | --------------------------- | | 10+ options, select one | `select` dropdown | | 3-5 options, select one, visual comparison helpful | `radio` buttons | | 2 options, select one | `radio` buttons or `switch` | | Select multiple from a list | `checkbox` (multiple) | | Accept terms/agreement | `checkbox` (single) | ## Complete Example Here's a product display configurator using all selection types: ```ts linkapp.config.ts theme={"system"} export default { settings: { title: "Product Display", elements: [ // Select: Many options { id: "category", inputType: "select", title: "Product Category", label: "Category", defaultValue: "all", options: [ { label: "All Products", value: "all" }, { label: "Electronics", value: "electronics" }, { label: "Clothing", value: "clothing" }, { label: "Books", value: "books" }, { label: "Home & Garden", value: "home" }, { label: "Sports", value: "sports" }, ], validation: { required: true }, }, // Radio: Few options, visual comparison { id: "layout", inputType: "radio", title: "Display Layout", options: [ { label: "Grid (3 columns)", value: "grid" }, { label: "List (full width)", value: "list" }, { label: "Carousel", value: "carousel" }, ], defaultValue: "grid", }, // Checkbox: Multiple selections { id: "displayOptions", inputType: "checkbox", title: "Display Options", description: "Choose what to show for each product", defaultValue: ["price", "rating"], options: [ { label: "Show price", value: "price" }, { label: "Show rating", value: "rating" }, { label: "Show reviews count", value: "reviews" }, { label: "Show availability", value: "availability" }, ], }, ], }, }; ``` ## Type Safety Define exact types for your selections: ```ts theme={"system"} type ProductSettings = { category: 'all' | 'electronics' | 'clothing' | 'books' | 'home' | 'sports' layout: 'grid' | 'list' | 'carousel' displayOptions: string[] // ['price', 'rating', 'reviews', 'availability'] } export default function ClassicLayout({ category, layout, displayOptions }: AppProps) { // TypeScript knows exact types ✨ const showPrice = displayOptions.includes('price') return } ``` ## Next Steps Text, textarea, URL, and number inputs Switch and link behavior elements Arrays, integrations, and conditional display Return to settings reference overview # Text Input Elements Source: https://docs.linktr.ee/essentials/settings/text-inputs Text, textarea, URL, and number input elements ## Overview Text input elements capture user-entered text and numbers. Choose the right type based on what you're collecting—each type provides appropriate validation and UI. | Element | Best For | Validation | | ---------- | -------------------------- | --------------- | | `text` | Short text (names, titles) | Length, pattern | | `textarea` | Long text (descriptions) | Length | | `url` | Web addresses | URL format | | `number` | Numeric values | Min/max | ## Text Input Single-line text input for short text like usernames, titles, or labels. ### Properties | Property | Type | Description | | -------------- | -------- | ------------------------------------- | | `id` | `string` | Unique identifier (becomes prop name) | | `inputType` | `'text'` | Must be `'text'` | | `title` | `string` | Field title | | `description` | `string` | Help text | | `label` | `string` | Input label | | `placeholder` | `string` | Placeholder text | | `defaultValue` | `string` | Initial value | | `validation` | `object` | Validation rules | ### Validation | Rule | Type | Description | | ----------- | --------- | ----------------------- | | `required` | `boolean` | Field must not be empty | | `minLength` | `number` | Minimum characters | | `maxLength` | `number` | Maximum characters | | `pattern` | `string` | Regex pattern to match | ### Example ```ts linkapp.config.ts theme={"system"} { id: "username", inputType: "text", title: "Username", description: "Your display name", label: "Username", placeholder: "johndoe", defaultValue: "", validation: { required: true, minLength: 3, maxLength: 20, pattern: "^[a-zA-Z0-9_]+$" // Alphanumeric and underscores } } ``` ### Usage in Layout ```tsx app/expanded.tsx theme={"system"} type Settings = { username: string; }; export default function ClassicLayout({ username }: AppProps) { return
Welcome, {username}!
; } ``` ### Common Patterns **Display name:** ```ts theme={"system"} { id: "displayName", inputType: "text", title: "Display Name", label: "Name", placeholder: "John Doe", validation: { required: true, maxLength: 50 } } ``` **Title or heading:** ```ts theme={"system"} { id: "title", inputType: "text", title: "LinkApp Title", label: "Title", placeholder: "Enter a title", defaultValue: "My LinkApp", validation: { maxLength: 100 } } ``` **Custom label:** ```ts theme={"system"} { id: "buttonText", inputType: "text", title: "Button Text", label: "Text", placeholder: "Click me", defaultValue: "Learn More", validation: { required: true, maxLength: 30 } } ``` ## Textarea Multi-line text input for longer content like descriptions or messages. ### Properties | Property | Type | Description | | -------------- | ------------ | -------------------- | | `id` | `string` | Unique identifier | | `inputType` | `'textarea'` | Must be `'textarea'` | | `title` | `string` | Field title | | `description` | `string` | Help text | | `label` | `string` | Input label | | `placeholder` | `string` | Placeholder text | | `defaultValue` | `string` | Initial value | | `validation` | `object` | Validation rules | ### Validation | Rule | Type | Description | | ----------- | --------- | ----------------------- | | `required` | `boolean` | Field must not be empty | | `minLength` | `number` | Minimum characters | | `maxLength` | `number` | Maximum characters | ### Example ```ts linkapp.config.ts theme={"system"} { id: "bio", inputType: "textarea", title: "Bio", description: "Tell users about yourself", label: "Your bio", placeholder: "I'm a creator who...", defaultValue: "", validation: { maxLength: 500 } } ``` ### Usage in Layout ```tsx app/expanded.tsx theme={"system"} type Settings = { bio: string; }; export default function ClassicLayout({ bio }: AppProps) { return (

{bio}

); } ``` ### Common Patterns **Description field:** ```ts theme={"system"} { id: "description", inputType: "textarea", title: "Description", label: "Describe your app", placeholder: "This app helps you...", validation: { required: true, maxLength: 300 } } ``` **Custom message:** ```ts theme={"system"} { id: "welcomeMessage", inputType: "textarea", title: "Welcome Message", label: "Message", placeholder: "Welcome! Here's what you need to know...", validation: { maxLength: 1000 } } ``` ## URL Input URL input with built-in validation to ensure valid HTTP/HTTPS addresses. ### Properties | Property | Type | Description | | -------------- | -------- | ----------------- | | `id` | `string` | Unique identifier | | `inputType` | `'url'` | Must be `'url'` | | `title` | `string` | Field title | | `description` | `string` | Help text | | `label` | `string` | Input label | | `placeholder` | `string` | Placeholder URL | | `defaultValue` | `string` | Initial URL | | `validation` | `object` | Validation rules | ### Validation | Rule | Type | Description | | ---------- | --------- | ------------------------ | | `required` | `boolean` | Field must not be empty | | `pattern` | `string` | Additional regex pattern | URL inputs automatically validate that the value starts with `http://` or `https://`. Use `pattern` for additional constraints like requiring HTTPS. ### Example ```ts linkapp.config.ts theme={"system"} { id: "websiteUrl", inputType: "url", title: "Website URL", description: "Your website address", label: "Website", placeholder: "https://example.com", defaultValue: "", validation: { required: true, pattern: "^https://.*" // Require HTTPS } } ``` ### Usage in Layout ```tsx app/expanded.tsx theme={"system"} type Settings = { websiteUrl: string; }; export default function ClassicLayout({ websiteUrl }: AppProps) { return ( Visit Website ); } ``` ### Common Patterns **External link:** ```ts theme={"system"} { id: "externalUrl", inputType: "url", title: "External Link", label: "URL", placeholder: "https://example.com", validation: { required: true } } ``` **API endpoint:** ```ts theme={"system"} { id: "apiEndpoint", inputType: "url", title: "API Endpoint", description: "Your API base URL", label: "Endpoint", placeholder: "https://api.example.com", validation: { required: true, pattern: "^https://.*" } } ``` ## Number Input Numeric input with min/max validation. ### Properties | Property | Type | Description | | -------------- | ------------------ | ------------------ | | `id` | `string` | Unique identifier | | `inputType` | `'number'` | Must be `'number'` | | `title` | `string` | Field title | | `description` | `string` | Help text | | `label` | `string` | Input label | | `placeholder` | `number \| string` | Placeholder value | | `defaultValue` | `number` | Initial value | | `validation` | `object` | Validation rules | ### Validation | Rule | Type | Description | | ---------- | --------- | ------------------------- | | `required` | `boolean` | Field must not be empty | | `min` | `number` | Minimum value (inclusive) | | `max` | `number` | Maximum value (inclusive) | ### Example ```ts linkapp.config.ts theme={"system"} { id: "itemCount", inputType: "number", title: "Number of Items", description: "How many items to display", label: "Count", placeholder: 10, defaultValue: 5, validation: { required: true, min: 1, max: 100 } } ``` ### Usage in Layout ```tsx app/expanded.tsx theme={"system"} type Settings = { itemCount: number; }; export default function ClassicLayout({ itemCount }: AppProps) { return (
Showing {itemCount} items {Array.from({ length: itemCount }).map((_, i) => (
Item {i + 1}
))}
); } ``` ### Common Patterns **Item limit:** ```ts theme={"system"} { id: "maxItems", inputType: "number", title: "Maximum Items", label: "Max", defaultValue: 10, validation: { min: 1, max: 50 } } ``` **Timeout/duration:** ```ts theme={"system"} { id: "timeoutSeconds", inputType: "number", title: "Timeout", description: "Timeout in seconds", label: "Seconds", defaultValue: 30, validation: { min: 5, max: 300 } } ``` **Percentage:** ```ts theme={"system"} { id: "opacity", inputType: "number", title: "Opacity", description: "Opacity percentage (0-100)", label: "Opacity %", defaultValue: 100, validation: { min: 0, max: 100 } } ``` ## Complete Example Here's a contact form using multiple text input types: ```ts linkapp.config.ts theme={"system"} export default { settings: { title: "Contact Form", elements: [ { id: "formTitle", inputType: "text", title: "Form Title", label: "Title", defaultValue: "Get in Touch", validation: { required: true, maxLength: 50 }, }, { id: "description", inputType: "textarea", title: "Form Description", label: "Description", placeholder: "We'd love to hear from you...", validation: { maxLength: 200 }, }, { id: "submitUrl", inputType: "url", title: "Submit URL", description: "Where form submissions are sent", label: "Endpoint", validation: { required: true, pattern: "^https://.*" }, }, { id: "apiKey", inputType: "text", title: "API Key", description: "Your form service API key", placeholder: "Enter your API key", label: "Key", validation: { required: true, minLength: 20 }, }, { id: "maxSubmissions", inputType: "number", title: "Submission Limit", description: "Maximum submissions per user", label: "Max", defaultValue: 1, validation: { min: 1, max: 10 }, }, ], }, }; ``` ## Validation Error Messages When validation fails, users see helpful messages: | Rule | Example Error | | --------------------- | -------------------------------- | | `required: true` | "This field is required" | | `minLength: 3` | "Must be at least 3 characters" | | `maxLength: 100` | "Must be at most 100 characters" | | `min: 1` | "Must be at least 1" | | `max: 100` | "Must be at most 100" | | `pattern: '^[a-z]+$'` | "Invalid format" | Add clear `description` text to help users understand what's expected and avoid validation errors. ## Next Steps Select, radio, and checkbox elements Switch and link behavior elements Arrays, integrations, and conditional display Return to settings reference overview # Toggle Elements Source: https://docs.linktr.ee/essentials/settings/toggles Switch and link behavior elements for on/off controls ## Overview Toggle elements provide simple on/off controls. They're perfect for enabling/disabling features or choosing between two states. | Element | Use Case | Returns | | -------------- | ----------------------------------------- | -------------------------------- | | `switch` | Enable/disable features | `boolean` | | `linkBehavior` | Embed vs. direct link (Linktree-specific) | `'embedLabel' \| 'linkOffLabel'` | ## Switch Toggle Boolean toggle for enabling/disabling features. Provides a visual on/off switch UI. ### Properties | Property | Type | Description | | -------------- | ---------- | -------------------------------------- | | `id` | `string` | Unique identifier (becomes prop name) | | `inputType` | `'switch'` | Must be `'switch'` | | `title` | `string` | Field title | | `description` | `string` | Help text | | `label` | `string` | Label text next to switch | | `defaultValue` | `boolean` | Initial state (true = on, false = off) | | `validation` | `object` | Validation rules | ### Validation | Rule | Type | Description | | ---------- | --------- | -------------------------------------- | | `required` | `boolean` | Switch must be ON (value must be true) | Setting `required: true` means the switch **must be ON**. This is useful for terms acceptance or required opt-ins. Most switches should not be required. ### Example ```ts linkapp.config.ts theme={"system"} { id: "showTitle", inputType: "switch", title: "Display Options", description: "Toggle title visibility", label: "Show title", defaultValue: true, validation: { required: false // Optional - can be on or off } } ``` ### Usage in Layout ```tsx app/expanded.tsx theme={"system"} type Settings = { showTitle: boolean } export default function ClassicLayout({ showTitle }: AppProps) { return (
{showTitle &&

My LinkApp

}

Content

) } ``` ### Common Patterns **Feature toggle:** ```ts theme={"system"} { id: "enableNotifications", inputType: "switch", title: "Notifications", label: "Enable email notifications", defaultValue: false } ``` **Display option:** ```ts theme={"system"} { id: "showDescription", inputType: "switch", title: "Display Options", description: "Show or hide the description text", label: "Show description", defaultValue: true } ``` **Required acceptance:** ```ts theme={"system"} { id: "agreeToTerms", inputType: "switch", title: "Terms of Service", description: "You must accept the terms to continue", label: "I agree to the Terms of Service", defaultValue: false, validation: { required: true // Must be ON } } ``` **Privacy setting:** ```ts theme={"system"} { id: "publicProfile", inputType: "switch", title: "Privacy", description: "Make your profile visible to everyone", label: "Public profile", defaultValue: false } ``` ### Multiple Switches Group related switches under one title: ```ts theme={"system"} elements: [ { id: "showTitle", inputType: "switch", title: "Display Options", // Section title description: "Control what appears on your LinkApp", label: "Show title", defaultValue: true }, { id: "showDescription", inputType: "switch", title: "", // Same section, no title label: "Show description", defaultValue: true }, { id: "showAuthor", inputType: "switch", title: "", // Same section label: "Show author name", defaultValue: false } ] ``` **Usage:** ```tsx theme={"system"} type Settings = { showTitle: boolean showDescription: boolean showAuthor: boolean } export default function ClassicLayout({ showTitle, showDescription, showAuthor }: AppProps) { return (
{showTitle &&

Title

} {showDescription &&

Description

} {showAuthor && By Author}
) } ``` ### Switch vs. Checkbox When to use switch vs. single checkbox: | Use Switch When | Use Checkbox When | | ---------------------------- | ------------------------- | | Enabling/disabling a feature | Accepting terms/agreement | | Toggling display options | Opting into something | | Turning something on/off | Confirming understanding | | Immediate effect expected | Explicit consent required | ```ts theme={"system"} // ✅ Switch - for feature toggle { id: "enableDarkMode", inputType: "switch", label: "Enable dark mode" } // ✅ Checkbox - for acceptance { id: "terms", inputType: "checkbox", options: [{ label: "I agree to terms", value: "agreed" }] } ``` ## Link Behavior Linktree-specific control for how links behave when clicked. Users choose between showing your LinkApp UI (embed) or navigating directly to the URL (link off). ### Properties | Property | Type | Description | | -------------------- | -------------------------------- | -------------------------- | | `id` | `string` | Unique identifier | | `inputType` | `'linkBehavior'` | Must be `'linkBehavior'` | | `title` | `string` | Field title | | `description` | `string` | Help text | | `label` | `string` | Label for radio group | | `defaultValue` | `'embedLabel' \| 'linkOffLabel'` | Initial behavior | | `linkBehaviorLabels` | `object` | Labels for the two options | | `validation` | `object` | Validation rules | ### Link Behavior Labels | Property | Type | Description | | -------------- | -------- | ---------------------------- | | `embedLabel` | `string` | Label for showing LinkApp UI | | `linkOffLabel` | `string` | Label for direct navigation | **Link Behavior** lets users choose: * **Embed** (`embedLabel`): Display your LinkApp's UI when clicked (renders your layout) * **Link Off** (`linkOffLabel`): Navigate directly to the URL (bypass your layout) If not specified, defaults to `embedLabel` (show your LinkApp). ### Example ```ts linkapp.config.ts theme={"system"} { id: "linkBehavior", inputType: "linkBehavior", title: "Link Behavior", description: "How should this link behave when clicked?", label: "Click behavior", defaultValue: "embedLabel", linkBehaviorLabels: { embedLabel: "Show custom video player", linkOffLabel: "Go directly to YouTube" }, validation: { required: true } } ``` ### Usage in Layout ```tsx app/expanded.tsx theme={"system"} type Settings = { linkBehavior: 'embedLabel' | 'linkOffLabel' } export default function ClassicLayout({ linkBehavior, linkUrl }: AppProps) { // If user chose "link off", redirect immediately if (linkBehavior === 'linkOffLabel') { window.location.href = linkUrl return null } // Otherwise, show your custom UI return (
) } ``` ### Common Use Cases **Video embed:** ```ts theme={"system"} { id: "videoDisplay", inputType: "linkBehavior", title: "Video Display", description: "Choose how to display YouTube videos", linkBehaviorLabels: { embedLabel: "Show video player on Linktree", linkOffLabel: "Open YouTube in new tab" }, defaultValue: "embedLabel" } ``` **Content preview:** ```ts theme={"system"} { id: "contentBehavior", inputType: "linkBehavior", title: "Content Preview", linkBehaviorLabels: { embedLabel: "Show article preview", linkOffLabel: "Go directly to article" }, defaultValue: "embedLabel" } ``` **Social media:** ```ts theme={"system"} { id: "socialEmbed", inputType: "linkBehavior", title: "Social Post Display", description: "Display Instagram post or link to it", linkBehaviorLabels: { embedLabel: "Embed Instagram post", linkOffLabel: "Open Instagram post" }, defaultValue: "embedLabel" } ``` ### When to Use Use `linkBehavior` when: ✅ Your LinkApp embeds or displays content from the URL ✅ Users might want to bypass your UI and go straight to the source ✅ You're building a media player, content previewer, or social embed Don't use when: ❌ Your LinkApp doesn't use the `linkUrl` at all ❌ Going directly to the URL doesn't make sense ❌ Your LinkApp is purely decorative ### Validation | Rule | Type | Description | | ---------- | --------- | --------------------------- | | `required` | `boolean` | A behavior must be selected | Typically, `linkBehavior` should have `validation: { required: true }` so users explicitly choose their preference. ## Complete Example Here's a video embed LinkApp using both toggle types: ```ts linkapp.config.ts theme={"system"} export default { settings: { title: "Video Player", uses_url: true, // This app uses the linkUrl prop elements: [ // Link behavior - embed vs. direct link { id: "linkBehavior", inputType: "linkBehavior", title: "Link Behavior", description: "Choose how videos open", linkBehaviorLabels: { embedLabel: "Play video in Linktree", linkOffLabel: "Open YouTube directly" }, defaultValue: "embedLabel", validation: { required: true } }, // Feature switches { id: "autoplay", inputType: "switch", title: "Playback Options", label: "Autoplay video", defaultValue: false }, { id: "showControls", inputType: "switch", title: "", // Same section label: "Show player controls", defaultValue: true }, { id: "loop", inputType: "switch", title: "", // Same section label: "Loop video", defaultValue: false } ] } } ``` **Layout implementation:** ```tsx app/expanded.tsx theme={"system"} type VideoSettings = { linkBehavior: 'embedLabel' | 'linkOffLabel' autoplay: boolean showControls: boolean loop: boolean } export default function ClassicLayout({ linkBehavior, linkUrl, autoplay, showControls, loop }: AppProps) { // Direct link - redirect to YouTube if (linkBehavior === 'linkOffLabel') { window.location.href = linkUrl return null } // Embed - show custom player return (
) } ``` ## Best Practices ### Use Clear Labels ```ts theme={"system"} // ✅ Good - descriptive { label: "Enable email notifications" } { label: "Show author name and profile picture" } // ❌ Unclear { label: "Notifications" } { label: "Author" } ``` ### Set Sensible Defaults ```ts theme={"system"} // ✅ Good - off by default for optional features { id: "betaFeatures", defaultValue: false } // ✅ Good - on by default for core features { id: "showTitle", defaultValue: true } ``` ### Group Related Switches Use a single `title` for the group, empty strings for subsequent items: ```ts theme={"system"} { id: "showTitle", title: "Display Options", // Group title label: "Show title" }, { id: "showDate", title: "", // Same group label: "Show date" } ``` ### Link Behavior Labels Should Be Specific ```ts theme={"system"} // ✅ Good - tells users exactly what happens linkBehaviorLabels: { embedLabel: "Play video in Linktree", linkOffLabel: "Open YouTube in new tab" } // ❌ Too generic linkBehaviorLabels: { embedLabel: "Embed", linkOffLabel: "Link" } ``` ## Type Safety Define boolean types for switches: ```ts theme={"system"} type MySettings = { showTitle: boolean enableNotifications: boolean autoplay: boolean linkBehavior: 'embedLabel' | 'linkOffLabel' } export default function ClassicLayout(props: AppProps) { // Full type safety ✨ const { showTitle, enableNotifications, autoplay, linkBehavior } = props return
...
} ``` ## Next Steps Text, textarea, URL, and number inputs Select, radio, and checkbox elements Arrays, integrations, and conditional display Return to settings reference overview # Installation Source: https://docs.linktr.ee/installation Build custom LinkApps for Linktree in minutes Build interactive LinkApps for Linktree profiles. ## What you need * **Node.js 18** or newer ([download here](https://nodejs.org/)) * **A text editor** ([VS Code](https://code.visualstudio.com/) is free and works great) * **A Linktree account** ([sign up free](https://linktr.ee/register)) Not sure if you have Node.js? Open your terminal and type `node --version`. If you see a number like `v18.0.0` or higher, you're good to go! ## Quick start Run these 3 commands to create and preview your first LinkApp: ```bash npm theme={"system"} npx @linktr.ee/create-linkapp my-app cd my-app npm run dev ``` ```bash pnpm theme={"system"} pnpm create @linktr.ee/linkapp my-app cd my-app pnpm dev ``` ```bash yarn theme={"system"} yarn create @linktr.ee/linkapp my-app cd my-app yarn dev ``` ```bash bun theme={"system"} bunx @linktr.ee/create-linkapp my-app cd my-app bun dev ``` Your browser opens automatically to preview your app. ## What are LinkApps? LinkApps appear on Linktree profiles. Instead of a link that takes people away, they can interact right on your Linktree page. **Examples:** * Video players (YouTube, Vimeo, TikTok) * Product showcases with prices * FAQ sections * Contact forms * Social media feeds **The difference:** * Normal link → Click → Leave Linktree * LinkApp → Click → Stay and interact ## Create your first LinkApp Run this command to start: ```bash theme={"system"} npx @linktr.ee/create-linkapp ``` You'll answer 3 quick questions: ```txt theme={"system"} ? Project name › my-app ? Initialize git repository? › Yes ? Install dependencies? › Yes ``` **What these mean:** * **Project name** - Your app's folder name (like `my-video-player`) * **Git repository** - Version control (say Yes) * **Install dependencies** - Download required files (say Yes) After a minute, you'll see: ``` ✔ Created project at /path/to/my-app ✔ Installed dependencies Success! Created my-app Get started: cd my-app npm run dev ``` **New to terminal?** No problem! * **Mac**: Press `Cmd + Space`, type "Terminal", hit Enter * **Windows**: Press `Win + R`, type "cmd", hit Enter Then copy-paste the commands above. ## Your project files Your new project includes: * **`app/expanded.tsx`** - Your main app layout (start editing here!) * **`linkapp.config.ts`** - Your app's name and settings * **`components/`** - For reusable UI components * **`app/featured.tsx`** - Optional hero layout ## Preview your app Start the preview: ```bash npm theme={"system"} cd my-app npm run dev ``` ```bash pnpm theme={"system"} cd my-app pnpm dev ``` ```bash yarn theme={"system"} cd my-app yarn dev ``` ```bash bun theme={"system"} cd my-app bun dev ``` Your browser opens showing your app inside a fake Linktree profile. You can: * Test light/dark themes * See how it looks on different layouts * Edit code and see changes instantly Leave this running! Every time you save your code, the preview updates automatically. ## Edit your app Open `app/expanded.tsx` in your editor and change the text: ```tsx app/expanded.tsx theme={"system"} import type { AppProps } from "@linktr.ee/linkapp"; export default function ClassicLayout({ __linkUrl, theme }: AppProps) { return (

👋 Hello!

This is my custom LinkApp!

Visit my website →
); } ``` Save the file and watch your preview update instantly! **What's happening:** - `__linkUrl` - The URL they'll click to ## Deploy to Linktree ### Step 1: Log in ```bash npm theme={"system"} npx @linktr.ee/linkapp login ``` ```bash pnpm theme={"system"} pnpm @linktr.ee/linkapp login ``` ```bash yarn theme={"system"} yarn @linktr.ee/linkapp login ``` ```bash bun theme={"system"} bunx @linktr.ee/linkapp login ``` A browser window opens. Log in with your Linktree account. You only do this once. Your login is saved. ### Step 2: Deploy ```bash npm theme={"system"} npm run deploy ``` ```bash pnpm theme={"system"} pnpm deploy ``` ```bash yarn theme={"system"} yarn deploy ``` ```bash bun theme={"system"} bun deploy ``` This builds your app and uploads it to Linktree. You'll get a test link: ``` ✔ Deployed successfully! Test link: https://linktr.ee/admin?action=create-link&linkType=my-app ``` Click the test link to add your app to your Linktree profile! Your app starts as a **DRAFT**. Only you can see it. To make it public for all Linktree users, contact Linktree for approval. ## Add pre-built components Want buttons, forms, or other UI elements? Add them instantly: ```bash npm theme={"system"} npx @linktr.ee/linkapp add button ``` ```bash pnpm theme={"system"} pnpm @linktr.ee/linkapp add button ``` ```bash yarn theme={"system"} yarn @linktr.ee/linkapp add button ``` ```bash bun theme={"system"} bunx @linktr.ee/linkapp add button ``` Then use it in your code: ```tsx theme={"system"} import { Button } from "@/components/ui/button"; export default function MyApp() { return ; } ``` **Available:** `button`, `switch` (more coming soon!) ## Next steps Classic vs featured layouts Configure your app settings All available commands Browse UI components ## Common issues **Preview won't open?** * Make sure Node.js 18+ is installed * Check nothing else is using port 3001 **Changes not showing?** * Save your file in the editor * Check the terminal for errors **Deploy failed?** * Run `npm run build` first to check for errors * Make sure you ran `npx @linktr.ee/linkapp login` **Need help?** * [Full documentation](https://docs.linktr.ee/create-link-app) * Contact Linktree developer support