> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useparagon.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SharePoint

> Connect to your users' SharePoint accounts.

export const integration_0 = "SharePoint"

export const IntegrationsCompatibility = ({workflows = true, actionkit = false, proxy = true, managedSync = false, authType = "Basic Auth", integrationName: integrationNameProp, integrationSlug: integrationSlugProp}) => {
  const FEATURE_REQUEST_ENDPOINT = "https://agosnlmllwykglihfhiw.supabase.co/functions/v1/handle-vote";
  const FEATURE_REQUEST_HEADERS = {
    "Content-Type": "application/json",
    "Authorization": `Bearer sb_publishable_DlIzCjWe8NiqjZnLziegbg_P-w5L9X4`,
    'apikey': `sb_pubishable_DlIzCjWe8NiqjZnLziegbg_P-w5L9X4`
  };
  const SESSION_VOTE_PREFIX = "paragon_compat_vote:";
  const PURPLE = "rgb(102, 69, 230)";
  const slugifyFeature = label => label.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
  const integrationKeyFromPathname = pathname => {
    if (!pathname) return "";
    const match = pathname.match(/\/(?:resources\/)?integrations\/([^/?#]+)/);
    return match ? decodeURIComponent(match[1]).replace(/\/$/, "") : "";
  };
  const voteStorageKey = (integrationKey, featureKey) => `${SESSION_VOTE_PREFIX}${integrationKey}:${featureKey}`;
  const readVoteFromStorage = (integrationKey, featureKey) => {
    if (typeof sessionStorage === "undefined") return false;
    try {
      return sessionStorage.getItem(voteStorageKey(integrationKey, featureKey)) === "1";
    } catch {
      return false;
    }
  };
  const writeVoteToStorage = (integrationKey, featureKey) => {
    try {
      sessionStorage.setItem(voteStorageKey(integrationKey, featureKey), "1");
    } catch {}
  };
  const readPageTitleFallback = () => {
    if (typeof document === "undefined") return "";
    const h1 = document.querySelector("article h1") || document.querySelector("main h1") || document.querySelector('[class*="title"] h1') || document.querySelector("h1");
    const text = h1?.textContent?.trim();
    return text || "";
  };
  const getRuntimeConfig = () => {
    if (typeof window === "undefined") return null;
    return window.__PARAGON_FEATURE_REQUEST__ ?? null;
  };
  const resolveEndpoint = () => {
    const rt = getRuntimeConfig();
    if (rt?.endpoint) return String(rt.endpoint).trim();
    return String(FEATURE_REQUEST_ENDPOINT || "").trim();
  };
  const resolveHeaders = () => {
    const rt = getRuntimeConfig();
    const base = {
      ...FEATURE_REQUEST_HEADERS,
      ...rt?.headers && typeof rt.headers === "object" ? rt.headers : {}
    };
    return base;
  };
  const isValidEmail = s => (/^[^\s@]+@[^\s@]+\.[^\s@]+$/).test(String(s).trim());
  const renderCompatCheckSvg = (sizePx = 18) => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" fill={PURPLE} style={{
    width: `${sizePx}px`,
    height: `${sizePx}px`,
    flexShrink: 0,
    display: "block",
    verticalAlign: "middle",
    margin: "0 auto"
  }} aria-hidden>
      <path d="M320 576C178.6 576 64 461.4 64 320C64 178.6 178.6 64 320 64C461.4 64 576 178.6 576 320C576 461.4 461.4 576 320 576zM438 209.7C427.3 201.9 412.3 204.3 404.5 215L285.1 379.2L233 327.1C223.6 317.7 208.4 317.7 199.1 327.1C189.8 336.5 189.7 351.7 199.1 361L271.1 433C276.1 438 282.9 440.5 289.9 440C296.9 439.5 303.3 435.9 307.4 430.2L443.3 243.2C451.1 232.5 448.7 217.5 438 209.7z" />
    </svg>;
  const products = useMemo(() => [{
    label: "Managed Sync",
    value: managedSync
  }, {
    label: "ActionKit",
    value: actionkit
  }, {
    label: "Workflows",
    value: workflows
  }, {
    label: "Proxy API",
    value: proxy
  }, {
    label: "Auth Type",
    value: authType
  }], [actionkit, managedSync, workflows, proxy, authType]);
  const [mounted, setMounted] = useState(false);
  const [resolvedIntegrationKey, setResolvedIntegrationKey] = useState(() => integrationSlugProp?.trim() || "");
  const [resolvedDisplayName, setResolvedDisplayName] = useState(() => integrationNameProp?.trim() || "Integration");
  const [inlineFormOpen, setInlineFormOpen] = useState(false);
  const [formFeature, setFormFeature] = useState({
    featureLabel: "",
    featureKey: ""
  });
  const [email, setEmail] = useState("");
  const [description, setDescription] = useState("");
  const [submitting, setSubmitting] = useState(false);
  const [submitError, setSubmitError] = useState("");
  const [voteClickError, setVoteClickError] = useState("");
  const [voteLogging, setVoteLogging] = useState(false);
  const [loggingFeatureKey, setLoggingFeatureKey] = useState("");
  const [, bumpVoteUi] = useState(0);
  const inlineCardRef = useRef(null);
  useEffect(() => {
    setMounted(true);
    const path = typeof window !== "undefined" ? window.location.pathname : "";
    const fromPath = integrationSlugProp?.trim() || integrationKeyFromPathname(path);
    setResolvedIntegrationKey(fromPath);
    const fromDom = readPageTitleFallback();
    const name = integrationNameProp?.trim() || fromDom || (fromPath ? fromPath.replace(/-/g, " ") : "Integration");
    setResolvedDisplayName(name);
  }, [integrationNameProp, integrationSlugProp]);
  const dismissInlineForm = useCallback(() => {
    setInlineFormOpen(false);
    setSubmitError("");
  }, []);
  const handleRequestClick = useCallback(async ({featureLabel, featureKey}) => {
    setVoteClickError("");
    const integrationKey = resolvedIntegrationKey || integrationKeyFromPathname(typeof window !== "undefined" ? window.location.pathname : "");
    if (!integrationKey) {
      setVoteClickError("Could not determine integration. Set the integrationSlug prop on this page.");
      return;
    }
    if (readVoteFromStorage(integrationKey, featureKey)) {
      return;
    }
    setLoggingFeatureKey(featureKey);
    setVoteLogging(true);
    const votedAt = new Date().toISOString();
    const ctaPayload = {
      integration_key: integrationKey,
      integration_name: resolvedDisplayName,
      feature_key: featureKey,
      feature_label: featureLabel,
      vote_phase: "cta_click",
      voted_at: votedAt
    };
    try {
      const endpoint = resolveEndpoint();
      if (endpoint) {
        const res = await fetch(endpoint, {
          method: "POST",
          headers: resolveHeaders(),
          body: JSON.stringify(ctaPayload)
        });
        if (!res.ok) {
          const text = await res.text().catch(() => "");
          throw new Error(text || `Request failed (${res.status})`);
        }
      } else if (typeof console !== "undefined" && console.warn) {
        console.warn("[IntegrationsCompatibility] FEATURE_REQUEST_ENDPOINT is empty; vote not sent. Set endpoint or window.__PARAGON_FEATURE_REQUEST__.");
      }
      writeVoteToStorage(integrationKey, featureKey);
      bumpVoteUi(v => v + 1);
      setFormFeature({
        featureLabel,
        featureKey
      });
      setEmail("");
      setDescription("");
      setSubmitError("");
      setInlineFormOpen(true);
    } catch (err) {
      setVoteClickError(err instanceof Error ? err.message : "Could not log your vote. Please try again.");
    } finally {
      setVoteLogging(false);
      setLoggingFeatureKey("");
    }
  }, [resolvedIntegrationKey, resolvedDisplayName]);
  useEffect(() => {
    if (!inlineFormOpen) return undefined;
    const id = window.setTimeout(() => {
      inlineCardRef.current?.scrollIntoView?.({
        behavior: "smooth",
        block: "nearest"
      });
    }, 80);
    return () => window.clearTimeout(id);
  }, [inlineFormOpen]);
  const submitEnrichment = useCallback(async () => {
    const trimmed = email.trim();
    if (!isValidEmail(trimmed)) {
      setSubmitError("Please enter a valid work email.");
      return;
    }
    const integrationKey = resolvedIntegrationKey || integrationKeyFromPathname(typeof window !== "undefined" ? window.location.pathname : "");
    if (!integrationKey) {
      setSubmitError("Could not determine integration. Set the integrationSlug prop on this page.");
      return;
    }
    const {featureKey, featureLabel} = formFeature;
    const endpoint = resolveEndpoint();
    const enrichedAt = new Date().toISOString();
    const payload = {
      integration_key: integrationKey,
      integration_name: resolvedDisplayName,
      feature_key: featureKey,
      feature_label: featureLabel,
      vote_phase: "enrichment",
      email: trimmed,
      description: description.trim() || undefined,
      voted_at: enrichedAt,
      enriched_at: enrichedAt
    };
    setSubmitting(true);
    setSubmitError("");
    try {
      if (endpoint) {
        const res = await fetch(endpoint, {
          method: "POST",
          headers: resolveHeaders(),
          body: JSON.stringify(payload)
        });
        if (!res.ok) {
          const text = await res.text().catch(() => "");
          throw new Error(text || `Request failed (${res.status})`);
        }
      } else if (typeof console !== "undefined" && console.warn) {
        console.warn("[IntegrationsCompatibility] FEATURE_REQUEST_ENDPOINT is empty; enrichment not sent. Set endpoint or window.__PARAGON_FEATURE_REQUEST__.");
      }
      setInlineFormOpen(false);
      setEmail("");
      setDescription("");
    } catch (err) {
      setSubmitError(err instanceof Error ? err.message : "Something went wrong. Please try again.");
    } finally {
      setSubmitting(false);
    }
  }, [email, description, formFeature, resolvedDisplayName, resolvedIntegrationKey]);
  const renderCompatProductCell = ({product, integrationKey, onRequestClick, alignWide, voteLogging, loggingFeatureKey}) => {
    const isAuthType = product.label === "Auth Type";
    const href = typeof product.value === "string" && !isAuthType ? product.value : null;
    const featureKey = slugifyFeature(product.label);
    const alreadyVoted = !isAuthType && integrationKey && !href && !product.value ? readVoteFromStorage(integrationKey, featureKey) : false;
    const unsupported = !isAuthType && !href && !product.value;
    const cellInner = isAuthType ? product.value : href ? <div style={{
      display: "inline-flex",
      alignItems: "center",
      gap: "6px"
    }}>
        {renderCompatCheckSvg()}
        <a href={href} className="compat-doc-link">
          Docs
          <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M7 17l9.2-9.2M17 17V7H7" /></svg>
        </a>
      </div> : product.value ? renderCompatCheckSvg() : alreadyVoted ? <span style={{
      fontSize: "12px",
      fontWeight: 500,
      color: "rgba(55, 55, 58, 0.85)"
    }} className="compat-requested-text">Requested</span> : <button type="button" className="compat-pill" disabled={voteLogging && loggingFeatureKey === featureKey} onClick={() => onRequestClick({
      featureLabel: product.label,
      featureKey
    })} style={{
      cursor: voteLogging && loggingFeatureKey === featureKey ? "not-allowed" : "pointer",
      opacity: voteLogging && loggingFeatureKey === featureKey ? 0.65 : 1
    }}>
        <svg className="compat-pill-plus" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" style={{
      color: PURPLE
    }} aria-hidden>
          <line x1="12" y1="5" x2="12" y2="19"></line>
          <line x1="5" y1="12" x2="19" y2="12"></line>
        </svg>
        {voteLogging && loggingFeatureKey === featureKey ? "Submitting..." : "Request"}
      </button>;
    if (alignWide) {
      return <div style={{
        fontSize: "13px",
        padding: "8px 8px",
        minHeight: unsupported || alreadyVoted ? "40px" : undefined,
        flex: 1,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        textAlign: "center"
      }}>
          {cellInner}
        </div>;
    }
    return <span style={{
      fontSize: "13px",
      width: "80px",
      textAlign: "center",
      display: "inline-flex",
      justifyContent: "center"
    }}>
        {cellInner}
      </span>;
  };
  return <div style={{
    backgroundColor: "transparent",
    border: "0px solid rgba(225, 225, 229, 1)",
    borderRadius: "12px",
    padding: "0px 0px",
    overflow: "hidden"
  }}>
      <style>{`
        .compat-wide { display: flex; }
        .compat-narrow { display: none; }
        @media (max-width: 640px) {
          .compat-wide { display: none !important; }
          .compat-narrow { display: flex !important; }
        }
        
        .compat-doc-link {
          color: rgb(102, 69, 230);
          font-weight: 500;
          font-size: 13px;
          display: inline-flex;
          align-items: center;
          gap: 2px;
          text-decoration: none;
        }
        .compat-doc-link:hover {
          text-decoration: underline;
          text-underline-offset: 2px;
        }
        .dark .compat-doc-link {
          color: rgb(162, 140, 255);
        }

        .compat-pill {
          display: inline-flex;
          align-items: center;
          justify-content: center;
          gap: 4px;
          box-sizing: border-box;
          min-height: 32px;
          padding: 6px 12px;
          border-radius: 999px;
          border: 1px solid rgba(0, 0, 0, 0.12);
          background-color: rgba(250, 250, 249, 1);
          font-size: 12px;
          font-weight: 500;
          color: rgba(35, 35, 38, 1);
          font-family: inherit;
          line-height: 1.2;
          width: auto;
          text-decoration: none;
          margin: 0;
          transition: box-shadow 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
        }
        .compat-pill:hover:not(:disabled) {
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        }
        
        a.compat-doc-link {
          text-decoration: underline !important;
          text-underline-offset: 2px !important;
          border-bottom: none !important;
          box-shadow: none !important;
          background: none !important;
        }
        a.compat-doc-link:hover {
          opacity: 0.8;
        }

        .dark .compat-pill {
          background-color: rgba(255, 255, 255, 0.05);
          border-color: rgba(255, 255, 255, 0.1);
          color: rgba(255, 255, 255, 0.85);
        }
        .dark .compat-pill:hover:not(:disabled) {
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
          background-color: rgba(255, 255, 255, 0.1);
        }
        .dark .compat-pill-plus {
          color: rgba(255, 255, 255, 0.85) !important;
        }
        .dark .compat-requested-text {
          color: rgba(255, 255, 255, 0.6) !important;
        }

        .compat-form-card {
          margin-top: 24px;
          width: 100%;
          box-sizing: border-box;
          background-color: #fff;
          border-radius: 16px;
          border: 1px solid rgba(0, 0, 0, 0.1);
          box-shadow: 0 8px 24px rgba(0,0,0,0.06);
          padding: 24px;
        }
        .dark .compat-form-card {
          background-color: #0f1114;
          border-color: rgba(255, 255, 255, 0.1);
          box-shadow: 0 8px 24px rgba(0,0,0,0.4);
        }

        .compat-form-close-btn {
          flex-shrink: 0;
          display: inline-flex;
          align-items: center;
          justify-content: center;
          width: 36px;
          height: 36px;
          margin: 0;
          padding: 0;
          border-radius: 8px;
          background: #fff;
          color: rgba(35, 35, 38, 0.75);
          font-size: 24px;
          line-height: 1;
          font-family: inherit;
          border: none;
        }
        .dark .compat-form-close-btn {
          background: rgba(255,255,255,0.05);
          color: rgba(255,255,255,0.7);
        }

        .compat-form-title {
          margin: 0 0 16px;
          font-size: 20px;
          font-weight: 700;
          color: #111;
        }
        .dark .compat-form-title {
          color: rgba(255, 255, 255, 0.95);
        }

        .compat-form-text {
          display: block;
          margin: 0 0 20px;
          font-size: 14px;
          color: rgba(55, 55, 58, 0.72);
          line-height: 1.5;
        }
        .dark .compat-form-text {
          color: rgba(255, 255, 255, 0.6);
        }

        .compat-form-label {
          display: flex;
          flex-direction: column;
          gap: 6px;
          font-size: 13px;
          font-weight: 500;
          color: #333;
        }
        .dark .compat-form-label {
          color: rgba(255, 255, 255, 0.85);
        }

        .compat-form-input-readonly {
          width: 100%;
          box-sizing: border-box;
          padding: 10px 12px;
          border-radius: 8px;
          border: 1px solid rgba(0, 0, 0, 0.08);
          background-color: rgba(250, 248, 245, 0.95);
          font-size: 14px;
          color: rgba(35, 35, 38, 0.95);
        }
        .dark .compat-form-input-readonly {
          background-color: rgba(255, 255, 255, 0.05);
          border-color: rgba(255, 255, 255, 0.1);
          color: rgba(255, 255, 255, 0.7);
        }

        .compat-form-input {
          width: 100%;
          box-sizing: border-box;
          padding: 10px 12px;
          border-radius: 8px;
          border: 1px solid rgba(0, 0, 0, 0.15);
          background-color: #fff;
          font-size: 14px;
          font-family: inherit;
          color: #111;
        }
        .dark .compat-form-input {
          background-color: rgba(255, 255, 255, 0.05);
          border-color: rgba(255, 255, 255, 0.15);
          color: rgba(255, 255, 255, 0.95);
        }

        .compat-form-submit {
          padding: 10px 16px;
          border-radius: 8px;
          border: 1px solid rgba(0, 0, 0, 0.15);
          background: #fff;
          font-size: 14px;
          font-weight: 700;
          font-family: inherit;
          color: #111;
        }
        .dark .compat-form-submit {
          background: rgba(255, 255, 255, 0.1);
          border-color: rgba(255, 255, 255, 0.2);
          color: rgba(255, 255, 255, 0.95);
        }
        .compat-form-tag {
          display: inline-flex;
          align-items: center;
          gap: 6px;
          padding: 6px 10px;
          border-radius: 8px;
          background-color: rgba(102, 69, 230, 0.12);
          border: 1px solid rgba(102, 69, 230, 0.35);
          color: rgb(102, 69, 230);
          font-size: 12px;
          font-weight: 600;
        }
        .dark .compat-form-tag {
          background-color: rgba(162, 140, 255, 0.15);
          border-color: rgba(162, 140, 255, 0.4);
          color: rgb(180, 160, 255);
        }
      `}</style>

      <div className="compat-wide" style={{
    flexDirection: "row",
    gap: "0px"
  }}>
        {products.map(f => <div key={f.label} style={{
    flex: 1,
    minWidth: 0,
    textAlign: "center",
    display: "flex",
    flexDirection: "column"
  }}>
            <div style={{
    fontSize: "13px",
    fontWeight: 500,
    padding: "6px 8px",
    borderBottom: "1px solid rgba(102, 69, 230, 0.15)",
    overflow: "hidden",
    textOverflow: "ellipsis",
    whiteSpace: "nowrap",
    textAlign: "center"
  }}>
              {f.label}
            </div>
            {renderCompatProductCell({
    product: f,
    integrationKey: mounted ? resolvedIntegrationKey : "",
    onRequestClick: handleRequestClick,
    alignWide: true,
    voteLogging,
    loggingFeatureKey
  })}
          </div>)}
      </div>

      <div className="compat-narrow" style={{
    flexDirection: "column",
    gap: "0px"
  }}>
        <div style={{
    display: "flex",
    justifyContent: "space-between",
    borderBottom: "1px solid rgba(102, 69, 230, 0.15)",
    padding: "6px 0"
  }}>
          <span style={{
    fontSize: "13px",
    fontWeight: 500
  }}>Product</span>
          <span style={{
    fontSize: "13px",
    fontWeight: 500,
    width: "80px",
    textAlign: "center"
  }}>Supported</span>
        </div>
        {products.map(f => <div key={f.label} style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    padding: "5px 0"
  }}>
            <span style={{
    fontSize: "13px"
  }}>{f.label}</span>
            {renderCompatProductCell({
    product: f,
    integrationKey: mounted ? resolvedIntegrationKey : "",
    onRequestClick: handleRequestClick,
    alignWide: false,
    voteLogging,
    loggingFeatureKey
  })}
          </div>)}
      </div>

      {voteClickError ? <p style={{
    margin: "16px 0 0",
    fontSize: "13px",
    color: "#b42318"
  }} role="alert">
          {voteClickError}
        </p> : null}

      {inlineFormOpen ? <div ref={inlineCardRef} role="region" aria-labelledby="compat-feature-request-title" className="compat-form-card">
          <div style={{
    display: "flex",
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "space-between",
    gap: "12px",
    marginBottom: "16px"
  }}>
            <div className="compat-form-tag">
              Feature request
            </div>
            <button type="button" onClick={dismissInlineForm} disabled={submitting} aria-label="Close feature request form" className="compat-form-close-btn" style={{
    cursor: submitting ? "not-allowed" : "pointer"
  }}>
              <span aria-hidden>×</span>
            </button>
          </div>
          <h2 id="compat-feature-request-title" className="compat-form-title">
            Request support for {formFeature.featureLabel}
          </h2>
          <p className="compat-form-text">
            Your vote has been recorded. Add your email and an optional note and we'll notify you when support is added.
          </p>

          <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "16px"
  }}>
            <label className="compat-form-label">
              Integration
              <input type="text" readOnly value={resolvedDisplayName} className="compat-form-input-readonly" tabIndex={-1} />
            </label>
            <label className="compat-form-label">
              Feature
              <input type="text" readOnly value={formFeature.featureLabel} className="compat-form-input-readonly" tabIndex={-1} />
            </label>
            <label className="compat-form-label">
              Work email
              <input type="email" name="email" autoComplete="email" placeholder="you@company.com" value={email} onChange={e => setEmail(e.target.value)} className="compat-form-input" disabled={submitting} />
            </label>
            <label className="compat-form-label">
              Describe your use case (optional)
              <textarea placeholder="What would you build if this were supported?" value={description} onChange={e => setDescription(e.target.value)} rows={4} className="compat-form-input" style={{
    resize: "vertical",
    minHeight: "96px"
  }} disabled={submitting} />
            </label>
          </div>

          {submitError ? <p style={{
    margin: "14px 0 0",
    fontSize: "13px",
    color: "#b42318"
  }} role="alert">
              {submitError}
            </p> : null}

          <div style={{
    display: "flex",
    justifyContent: "flex-end",
    gap: "10px",
    marginTop: "22px",
    flexWrap: "wrap"
  }}>
            <button type="button" onClick={submitEnrichment} disabled={submitting} className="compat-form-submit" style={{
    cursor: submitting ? "not-allowed" : "pointer"
  }}>
              {submitting ? "Submitting…" : "Submit details"}
            </button>
          </div>
        </div> : null}
    </div>;
};

<IntegrationsCompatibility workflows={true} actionkit="/actionkit/integrations/sharepoint" proxy={true} managedSync="/managed-sync/integrations/sharepoint" authType="OAuth2" />

Setup Guide

You can find your SharePoint application credentials by visiting your Microsoft Azure Portal.

You'll need the following information to set up your SharePoint app with Paragon Connect:

* Client ID
* Client Secret
* Scopes Requested

### Prerequisites

* A [Microsoft Azure](https://azure.microsoft.com/) account

### Add the Redirect URL to your SharePoint app

Paragon provides a redirect URL to send information to your app. To add the redirect URL to your SharePoint app:

1. Copy the link under "**Redirect URL**" in your integration settings in Paragon. The Redirect URL is:

```
https://passport.useparagon.com/oauth
```

2. Log in to the [Microsoft Azure Portal](https://azure.microsoft.com/) using your Microsoft account.

3. Navigate to **All Services > App Registrations** and select your application.

4. Select **Authentication** from the sidebar.

5. Under **Platform configurations**, press the **"Add a platform"** button.

6. Select the **Web** platform.

7. Paste the Redirect URL from Step 1 under Redirect URIs.

8. Press the **Save** button at the top of the page.

### Generate a Client Secret

Since SharePoint does not automatically provide you with a Client Secret for your application, we'll need to make one. To get your Client Secret:

1. Navigate to **Manage > Certificates & secrets** in the sidebar.

2. Under **Client Secrets**, press the **+ New client secret** button.

3. Name your client credentials and select an expiry that works best for your application. Press **Add** to create your credentials.

4. Copy the displayed Client Secret under the **Value** column.

<Info>
  **Note:** You will need to periodically create new and update your Client Secret as they expire for all Microsoft integrations.
</Info>

### Enable Multi-tenancy to your SharePoint app

To allow Microsoft users from outside of your organization to connect to your SharePoint application, you must specify this as an option within the SharePoint application registration.

1. Log in to the [Microsoft Azure Portal](https://azure.microsoft.com/) using your Microsoft account.

2. Navigate to **All Services > App Registrations** and select your application.

3. Select **Authentication** from the sidebar.

4. Under **Supported account types**, press the **"Accounts in any organizational directory"** option.

5. Click Save.

### Using Application Permissions

Application Permissions can be used with SharePoint if your integration should make requests as an application (like a service account), rather than in the context of an authenticated user.

Using Application Permissions requires Connected Users to provide organization-wide admin consent for the application, by either:

* Requiring Entra ID administrators to follow the Connect Portal flow and checking "Consent on behalf of your organization" during the authorization flow
* OR directing admins to the `/adminconsent` URL (described below) to explicitly grant admin consent.

<Info>
  If you are only using Delegated Permissions (the default option to use the integration in the context of an authenticated user), you can skip this section and proceed to [Add your SharePoint app to Paragon](#add-your-sharepoint-app-to-paragon).
</Info>

<Steps>
  <Step title="Generate a certificate key pair">
    Locally on your machine, create a self-signed certificate and private key using `openssl`:

    ```bash theme={null}
    openssl req -x509 -newkey rsa:2048 \
      -keyout private-key.pem \
      -out certificate.pem \
      -days 365 -nodes \
      -subj "/CN=ParagonIntegration"
    ```

    This produces two files:

    * `private-key.pem` — the private key (keep this secret, you will provide it to Paragon)
    * `certificate.pem` — the public certificate (you will upload this to Azure AD)
  </Step>

  <Step title="Get the certificate thumbprint">
    Extract the SHA-1 thumbprint in hex format (this matches what Azure portal displays):

    ```bash theme={null}
    openssl x509 -in certificate.pem -fingerprint -sha1 -noout \
      | sed 's/sha1 Fingerprint=//;s/://g'
    ```

    Save the output (a 40-character hex string). You will provide this to Paragon.
  </Step>

  <Step title="Upload the certificate to Azure AD">
    1. In the [Azure Portal](https://portal.azure.com/), navigate to **All Services > App Registrations** and select your application.

    2. Navigate to **Manage > Certificates & secrets** in the sidebar.

    3. Select the **Certificates** tab.

    4. Click **Upload certificate** and select the `certificate.pem` file from Step 1.

    5. Confirm the displayed thumbprint matches the value from Step 2.
  </Step>

  <Step title="Add Application Permissions">
    1. In your app registration, navigate to **API permissions** in the sidebar.

    2. Click **Add a permission** > **Microsoft Graph** > **Application permissions**.

    3. Add the required scopes for your use case. For Managed Sync, add:
       * `Sites.Read.All`
       * `Files.Read.All`
       * `User.Read.All`
       * `Group.Read.All`

    4. Click **Add permissions**.
  </Step>

  <Step title="Grant admin consent">
    A tenant admin must grant consent for the Application Permissions. Direct your admin to visit the following URL in their browser:

    ```
    https://login.microsoftonline.com/{tenant-id}/adminconsent?client_id={client-id}&redirect_uri=https://passport.useparagon.com/oauth
    ```

    * Replace `{tenant-id}` with the Entra ID Directory (tenant) ID (or use `organizations`, if it is not known).
    * Replace `{client-id}` with the Client ID of your App Registration.

    The admin will be prompted to sign in and grant consent for the requested permissions.
  </Step>

  <Step title="Add your certificate to Paragon">
    Continue in the next section to [add your SharePoint app to Paragon](#add-your-sharepoint-app-to-paragon).
  </Step>
</Steps>

### Add your SharePoint app to Paragon

1. Select **SharePoint** from the **Integrations Catalog**.

2. Under **App Configuration > Configure**, fill out your credentials as follows:

   * **Client ID:** Overview > Application (client) ID on your Microsoft app registration.
   * **Client Secret:** The value generated in the **Certificates & secrets** section of your Microsoft app registration.
   * **Permissions:** The scopes you require for your application, as designated in the **API permissions** section of your Microsoft app registration.
     <Info>
       **Note:** You should only add the scopes you've requested in your app registration to Paragon. Any permissions changes will need to be made in both the Azure Portal and Paragon.
     </Info>

3. If you are using [Application Permissions](#using-application-permissions), additionally provide:
   * **Private Key:** The contents of `private-key.pem` from the certificate generation step.
   * **Certificate Thumbprint:** The 40-character SHA-1 hex thumbprint from Azure portal (no colons or spaces).

4. Press the purple "**Save Changes**" button to save your credentials.

<Frame>
  <img src="https://mintcdn.com/paragon/XNDe-bFT_2yOxy1A/assets/Connecting%20Sharepoint%20to%20Paragon%20Connect.png?fit=max&auto=format&n=XNDe-bFT_2yOxy1A&q=85&s=15a7135d44dc692e3778d5b9b8916e2b" alt="" width="2008" height="1182" data-path="assets/Connecting Sharepoint to Paragon Connect.png" />
</Frame>

## Connecting to SharePoint

Once your users have connected their SharePoint account, you can use the Paragon SDK to access the SharePoint API on behalf of connected users.

See the SharePoint [REST API documentation](https://docs.microsoft.com/en-us/graph/api/resources/sharepoint?view=graph-rest-1.0) for their full API reference.

Any SharePoint API endpoints can be accessed with the Paragon SDK as shown in this example.

```javascript theme={null}
// You can find your project ID in the Overview tab of any Integration

// Authenticate the user
paragon.authenticate(<ProjectId>, <UserToken>);

// Get organization’s default site
await paragon.request("sharepoint", "/sites/root", { 
  method: "GET",
});


// Get list under a site
await paragon.request("sharepoint", "/sites/{site-id}/lists", { 
  method: "GET",
});

// Create task in list
await paragon.request("sharepoint", "sites/{site-id}/lists/{list-id}/items", { 
  method: "POST",
  body: {
    "fields": {
      "Title": "Widget",
      "Color": "Purple",
      "Weight": 32
    }
}
});
```

## Building SharePoint workflows

Once your SharePoint account is connected, you can add steps to perform the following actions:

* Create Item
* Update Item
* Get Item by ID
* Get Items in a List
* Delete Item
* Create List
* Get List by ID
* Get Lists
* Create List Column
* Get List Columns

You can also use the [SharePoint Request](/workflows/requests#making-integration-requests) step to access any of SharePoint's API endpoints without the authentication piece.

When creating messages in SharePoint, you can reference data from previous steps by typing `{{` to invoke the variable menu.

## Using Webhook Triggers

Webhook triggers can be used to run workflows based on events in your users' Sharepoint account. For example, you might want to trigger a workflow whenever new items are created in Sharepoint to sync your users' Sharepoint items to your application in real-time.

<Frame>
  <img src="https://mintcdn.com/paragon/HSp5hB8tE4Z6e44m/assets/Sharepoint%20Triggers%20in%20Paragon%20Connect.png?fit=max&auto=format&n=HSp5hB8tE4Z6e44m&q=85&s=f46283e2f0f7ef5cbf047dcab1410860" alt="" width="1018" height="714" data-path="assets/Sharepoint Triggers in Paragon Connect.png" />
</Frame>

You can find the full list of Webhook Triggers for Sharepoint below:

* **Item Created**
* **Item Updated**
* **File Deleted**
* **Page Modified**

## User-Configured OAuth

{integration_0} supports [User-Configured OAuth](/resources/user-configured-oauth), which lets your end users connect their own OAuth app instead of the one you've configured for your Paragon integration. This is useful when your customers want to use their own OAuth app for security or scope customization.

To enable this, pass `"user-configured-oauth"` as one of the values in the `accountType` array when launching the install flow:

```js theme={null}
// Allow user-configured OAuth as an option (defaults to hidden)
paragon.installIntegration("sharepoint", {
  accountType: paragon
    .getAccountTypeOptions("sharepoint")
    .map((option) => option.id),
});

// Only allow user-configured OAuth accounts to connect
paragon.installIntegration("sharepoint", {
  accountType: ["user-configured-oauth"],
});
```

See the [User-Configured OAuth reference](/resources/user-configured-oauth) for full details on how it works and what your users will be prompted for.

## Using the SharePoint File Picker

You can allow your user to select files from their SharePoint account in your app with the SharePoint File Picker provided by the Paragon SDK.

**Showing the File Picker**

Use the Paragon SDK in your frontend application to show the File Picker in your app.

The SDK provides an `ExternalFilePicker` class to load SharePoint's JavaScript into your page and authenticate with your user's connected SharePoint account.

```javascript theme={null}
let picker = new paragon.ExternalFilePicker("sharepoint", {
    onFileSelect: (files) => {
        // Handle file selection
    }
});

// Loads external dependencies and user's access token
await picker.init();

// Open the File Picker
picker.open();
```

You can configure the File Picker to listen for additional callbacks or to restrict allowed file types. Learn more about configuring File Picker options in the [SDK Reference](/apis/api-reference#externalfilepicker).

#### Downloading the Selected File

The SharePoint File Picker callback will return a `Response` object describing the user's file picker interaction including an array of any files selected. Using this array of `fileIds`, you can use the [Proxy API](/apis/proxy) to perform an authenticated proxy requests to download the files.

<CodeGroup>
  ```javascript JavaScript theme={null}
  await paragon.request('sharepoint', '/_api/web/GetFileById(<fileID>)/$value', {
  	method: 'GET'
  });
  ```

  ```plain REST API theme={null}
  POST https://proxy.useparagon.com/projects/19d...012/sdk/proxy/sharepoint/_api/web/GetFileById(<fileID>)/$value

  Authorization: Bearer eyJ...
  Content-Type: application/json
  ```
</CodeGroup>
