Saleor Apps

repository·main·Indexed 41 days ago

https://github.com/saleor/apps

A monorepo of applications, integrations, and marketplace components that extend Saleor's core functionality via GraphQL and webhooks. Includes the Anonymizer App for PII scrubbing, AvaTax for tax automation, a CMS integration app, and a Dummy Payment App for testing the Transactions API.

Tokens
71K
Snippets
185
Records
279
Agent score
49%

What's inside Saleor Apps

  1. Overview of the Saleor Anonymizer App

    main

    The Saleor Anonymizer App allows you to anonymize customer personal data directly from the Saleor Dashboard. It performs the following actions:

    • Customer Data Scrambling: Scrambles order billing and shipping details. First name, last name, phone, street address line 2, company name, and district are cleared. Street address line 1 is replaced with Anonymized. City, postal code, country area, and country are kept intact. Email is replaced with a random UUID-based address using a configurable domain.
    • Checkout Deletion: Deletes guest and registered customer checkouts to remove PII from abandoned carts. This requires Saleor 3.23+.
    • Profile Deletion: Deletes the customer profile after orders and checkouts are processed.

    Configuration:

    • NEXT_PUBLIC_CUSTOMER_SCRAMBLE_DOMAIN: Controls the domain used for the random UUID-based email addresses.
  2. Overview of the Saleor Onboarding App

    main

    The Saleor Onboarding App is a dashboard extension that mounts a Store Readiness guide on the Saleor Dashboard home page via the HOMEPAGE_WIDGETS extension point. It provides a guided workflow for new merchants to complete their initial commerce setup.

    Primary Checklist Tracks

    1. Set up your sales channel: Configuring warehouse and shipping.
    2. Add your first product: Creating initial inventory.
    3. Connect payments: Integrating payment providers.
    4. Place a test order: (Optional) Verifying the flow.

    Secondary Tracks

    • Before you go live: Reviewing customer emails and templates.
    • Connect Paper storefront: Deploying and configuring Paper (displayed as a sibling card on wide screens).

    Builder Tools

    Under the "Building with the API?" section, the app provides shortcuts for:

    • GraphiQL
    • Creating a custom app / API token
    • Inviting staff

    How Progress is Tracked

    • Commerce Steps: Completion status is derived directly from live GraphQL data (e.g., checking channel stock, products, payment apps, and orders).
    • UI Preferences: Settings like whether the builder is open are persisted in user metadata under the onboarding key.
    • Checklist State: Expanded rows in the checklist are stored in local React state to prevent loading flicker during saves.
  3. How Dummy Payment App works with webhooks

    main

    The app uses webhooks to facilitate payment flows and dashboard interactions. It implements the following webhooks:

    Storefront-initiated flows:

    • PAYMENT_GATEWAY_INITIALIZE_SESSION
    • TRANSACTION_INITIALIZE_SESSION
    • TRANSACTION_PROCESS_SESSION

    Dashboard-initiated flows (simulating a real provider):

    • TRANSACTION_REFUND_REQUESTED
    • TRANSACTION_CHARGE_REQUESTED
    • TRANSACTION_CANCELATION_REQUESTED
  4. Understand Savebar implementation divergences in @saleor/ui-next

    main

    The Savebar component in @saleor/ui-next is derived from the saleor-dashboard repository but contains intentional architectural divergences to accommodate being rendered inside an App iframe rather than the main Dashboard shell.

    Key Differences from Dashboard:

    1. Positioning and Layout: Unlike the Dashboard which uses @radix-ui/react-portal and SavebarRefProvider to anchor the bar to the dashboard chrome, the App version uses position: fixed. This ensures the bar stays at the bottom of the iframe view even when forms are short. To prevent content overlap, DetailPageLayout's withSavebar higher-order component automatically reserves the necessary bottom padding.
    2. Button Labels: Action button labels require explicit children (they do not use react-intl defaults). If an errorLabel is not provided, it defaults to the plain string "Try again" instead of a translated message.
    3. ConfirmButton Behavior: The ConfirmButton maintains the Dashboard's transition-state logic:
      • Displays a Loader2 spinner (from lucide-react) while loading.
      • Holds a checkmark for 3 seconds on success.
      • Shows an error variant with a retry label on failure.
      • Blocks pointer events while locked.
      • Ignores the disabled prop while showing completed feedback to prevent color flickering.
  5. Technical Architecture of the Onboarding App

    main

    The Onboarding App is designed as a lightweight dashboard extension with the following technical characteristics:

    • Routing: Uses Next.js App Router for API endpoints (specifically /api/manifest) and Pages Router for the UI (src/pages/index.tsx, served at /).
    • Manifest Extension: It utilizes the HOMEPAGE_WIDGETS mount point with a WIDGET target. It responds to GET requests, where the staff JWT is automatically provided by AppBridge.
    • Client-side Execution: All GraphQL queries are executed directly in the browser using the user's JWT issued by AppBridge. The app does not use webhooks or an App Platform Layer (APL); it only exposes the manifest endpoint.
    • Layout: The home panel layout is designed to fill the Dashboard home panel without using WidgetResize to avoid layout sizing issues and loading flicker.
  6. Generate schema and typings

    main

    The app uses GraphQL code generation to create schemas and typed functions based on Saleor's GraphQL endpoint. Running the build or dev commands triggers this generation.

    Note: You should commit the generated folder to your repository to ensure queries remain consistent and to track schema changes.

  7. How SECRET_KEY rotation works

    main

    Saleor apps use a SECRET_KEY to encrypt configuration data. To rotate this key without losing access to existing data, the system uses a dual-key mechanism during the transition period.

    Environment Variables

    • SECRET_KEY: The current key. Used only to decrypt existing data.
    • NEW_SECRET_KEY: The target key. Used to encrypt all new writes.

    Rotation Lifecycle

    1. Encryption Logic: When NEW_SECRET_KEY is present, new writes are encrypted with it. Reads attempt to decrypt using NEW_SECRET_KEY first, falling back to SECRET_KEY if decryption fails (allowing access to legacy data).
    2. Re-encryption: A rotation script re-encrypts stored data from SECRET_KEY to NEW_SECRET_KEY.
    3. Finalization: Once rotation is complete, the environment variables are swapped so that SECRET_KEY holds the new value and NEW_SECRET_KEY is removed.
  8. Configure APL for storing registration data

    main

    When an app is registered, Saleor passes an auth token to the app. To store this token and maintain privileged access, the app uses an APL (App Persistence Layer). The storage method is determined by the APL environment variable.

    Available APL Options

    ValueDescription
    fileUses local file storage. No extra setup required. Recommended for local development only. Not suitable for production or multi-tenant apps.
    upstashUses Upstash Redis for storage. Supports multi-tenancy and is suitable for production. Requires UPSTASH_URL and UPSTASH_TOKEN environment variables.

    If you require a custom database, you must implement your own APL interface.

    # Example for Upstash APL
    APL=upstash
    UPSTASH_URL=your_upstash_url
    UPSTASH_TOKEN=your_upstash_token
  9. Use Zod branded types for type-safe primitives

    main

    To prevent accidental misuse of primitive values (like passing an appId where a saleorApiUrl is expected), use Zod's .brand() function. This creates distinct, type-safe primitives without the boilerplate of full classes.

    When to use branded types:

    • For simple value objects like URLs, IDs, or tokens.
    • Use a class instead if you require additional logic or conversion methods.

    When to use branded types in this repository:

    • When creating new apps.
    • When refactoring existing apps to improve type safety.
    import { z } from "zod";
    
    // 1. Define the schema with .brand()
    const saleorApiUrlSchema = z
      .string()
      .url()
      .endsWith("/graphql/")
      .refine((value) => value.startsWith("http://") || value.startsWith("https://"), {
        message: 'Invalid input: must start with "http://" or "https://"',
      })
      .brand("SaleorApiUrl");
    
    // 2. Create a factory function to parse/validate raw input
    export const createSaleorApiUrl = (raw: string) => saleorApiUrlSchema.parse(raw);
    
    // 3. Export the inferred type for use in function signatures
    export type SaleorApiUrl = z.infer<typeof saleorApiUrlSchema>;
    
    // 4. Usage in a function
    function setOtelAttrs(saleorApiUrl: SaleorApiUrl) {
        // saleorApiUrl is now type-safe and cannot be confused with a generic string
        tracer.setAttrs('saleorApiUrl', saleorApiUrl);
    }
    
    // 5. Implementation
    const url = createSaleorApiUrl("https://api.example.com/graphql/");
    setOtelAttrs(url);
  10. Authentication Model

    main
    The Anonymizer App runs entirely within a Dashboard iframe. It uses the token provided by the Saleor App Bridge for all requests to Saleor. It has no independent backend and does not persist authentication data. It uses a NoopAPL (located at src/lib/noop-apl.ts) to satisfy the App SDK requirements without storing state.
  11. Structure of the AsideInfoCard component

    main

    The AsideInfoCard follows a specific structural intent to maintain a lightweight appearance:

    • Body: Contains a title and content. It explicitly avoids the default2 header band style used in other components to ensure it does not look like a SettingsSection or DetailSettingsCard.
    • Optional Foldable Footer: Can include a foldable section for secondary 'how-to' content or additional documentation.