New: identify your users — now free on every planGet started →
Docs

One script. That’s the whole install.

Say So is a single script that loads after your page and never touches your styles or your bundle. Drop the tag in, and you’re collecting feedback.

Quick start

Grab your project id from your dashboard and paste this near the end of your page:

<script src="https://trysay.so/w.js" data-sayso="YOUR_PROJECT_ID" defer></script>

A floating button appears in the bottom-right. That’s it — feedback lands in your inbox with the page URL attached. Below: exactly where the tag goes in each stack.

Frameworks

It’s the same script everywhere — here’s where it goes in the stacks people use most.

React

Add the feedback widget to a React app (Vite or Create React App) in index.html.

<!-- index.html (Vite or CRA), before </body> -->
<script src="https://trysay.so/w.js" data-sayso="YOUR_PROJECT_ID" defer></script>

Next.js

Use next/script in your App Router root layout.

// app/layout.tsx
import Script from "next/script";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://trysay.so/w.js"
          data-sayso="YOUR_PROJECT_ID"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}

Vue

Add the widget to your Vue app's index.html.

<!-- index.html, before </body> -->
<script src="https://trysay.so/w.js" data-sayso="YOUR_PROJECT_ID" defer></script>

Nuxt

Register it in nuxt.config — no template edits needed.

// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    head: {
      script: [
        { src: "https://trysay.so/w.js", "data-sayso": "YOUR_PROJECT_ID", defer: true },
      ],
    },
  },
});

SvelteKit

Add it to src/app.html in your SvelteKit project.

<!-- src/app.html, before </body> -->
<script src="https://trysay.so/w.js" data-sayso="YOUR_PROJECT_ID" defer></script>

Astro

Add it to your Astro layout. is:inline stops Astro from bundling it.

<!-- src/layouts/Layout.astro, before </body> -->
<script src="https://trysay.so/w.js" data-sayso="YOUR_PROJECT_ID" defer is:inline></script>

Angular

Add it to src/index.html in your Angular project.

<!-- src/index.html, before </body> -->
<script src="https://trysay.so/w.js" data-sayso="YOUR_PROJECT_ID" defer></script>

No-code & website builders

No plugin, no build step — paste the snippet into the site’s custom-code box.

Webflow

  1. Open Project Settings → Custom Code.
  2. Paste the snippet into the Footer Code box.
  3. Save, then publish your site.
<script src="https://trysay.so/w.js" data-sayso="YOUR_PROJECT_ID" defer></script>

Framer

  1. Open Project Settings → General → Custom Code.
  2. Paste the snippet into the "End of <body> tag" box.
  3. Publish.
<script src="https://trysay.so/w.js" data-sayso="YOUR_PROJECT_ID" defer></script>

Shopify

  1. Online Store → Themes → ··· → Edit code.
  2. Open theme.liquid and paste the snippet just before the closing </body> tag.
  3. Save.
<script src="https://trysay.so/w.js" data-sayso="YOUR_PROJECT_ID" defer></script>

Logged-in customers: drop this in right after the script so feedback is tied to who left it.

{% if customer %}
<script>
  SaySo.identify({
    id: "{{ customer.id }}",
    email: "{{ customer.email }}",
  });
</script>
{% endif %}

WordPress

  1. Install a footer-scripts plugin such as WPCode or "Insert Headers and Footers".
  2. Paste the snippet into the plugin's Footer box.
  3. Save — no theme files to touch.
<script src="https://trysay.so/w.js" data-sayso="YOUR_PROJECT_ID" defer></script>

Logged-in users: with a PHP snippet (e.g. in footer.php or a PHP-snippet plugin).

<?php $u = wp_get_current_user(); if ( $u->ID ) : ?>
<script>
  SaySo.identify({
    id: "<?php echo esc_js( $u->ID ); ?>",
    email: "<?php echo esc_js( $u->user_email ); ?>",
  });
</script>
<?php endif; ?>

Wix

  1. Settings → Custom Code → + Add Custom Code.
  2. Paste the snippet, choose All pages, and set it to load at Body - end.
  3. Apply.
<script src="https://trysay.so/w.js" data-sayso="YOUR_PROJECT_ID" defer></script>

Carrd

  1. Add an Embed element (Carrd Pro) and set its type to Code.
  2. Paste the snippet into it.
  3. Publish.
<script src="https://trysay.so/w.js" data-sayso="YOUR_PROJECT_ID" defer></script>

Identify your users

Call SaySo.identify() so every submission carries who left it — no extra field for them to fill in.

<!-- after the widget script -->
<script>
  SaySo.identify({ id: "user_123", email: "ada@example.com" });
</script>

In a single-page app, call it once the user is logged in (the optional chaining guards against the script not being ready yet):

// call this once your user is logged in
window.SaySo?.identify({
  id: user.id,
  email: user.email,
});

Only id and email are stored, and they ride along on the next submission.

Open it from your own button

Prefer your own “Feedback” link instead of the floating launcher? Turn the launcher off and add a data attribute to any element.

<!-- 1. turn off the default floating launcher -->
<script src="https://trysay.so/w.js" data-sayso="YOUR_PROJECT_ID"
        data-sayso-launcher="false" defer></script>

<!-- 2a. open anchored next to your own element -->
<button data-sayso>Send feedback</button>

<!-- 2b. ...or open at the default corner spot -->
<button data-sayso-trigger>Feedback</button>
  • data-sayso — opens the panel anchored next to that element.
  • data-sayso-trigger — opens at the default corner position.

Appearance

Accent color, button style, corner radius, and position all live in Project Settings — no code needed. The widget matches your brand automatically.

If you’d rather hard-code it per embed, override on the script tag:

<script src="https://trysay.so/w.js" data-sayso="YOUR_PROJECT_ID"
        data-sayso-accent="#7C3AED" defer></script>

Runtime API

SaySo.identify({ id, email })Attach the signed-in user to their feedback.
SaySo.setAccent("#hex")Recolor the widget at runtime.
SaySo.setAccent("#7C3AED");

Linear — push feedback to issues

Pro

Say So stays your system of record. Triage in the inbox, then push the items worth tracking into Linear as issues — and let Linear’s issue state flow back to the feedback’s status (which emails the person who left it). It’s two-way: moving the status in Say So also moves the linked issue.

Connect it (in your dashboard)

  1. In Linear, go to Settings → Security & access → Personal API keys and create a key (use an admin key so status can sync back).
  2. In Say So, open a project’s Settings → Integrations tab, paste the key into Linear, and connect. We store it encrypted.
  3. Pick the team new issues should be filed into, and save.

Use it

  1. On any feedback item, click Create Linear issue. The card then links straight to the issue (e.g. ENG-123).
  2. Move that issue through your Linear workflow. When it lands in a Done or Canceled state, the feedback’s status updates automatically and the submitter is emailed.

Statuses map by Linear’s universal state type, so it works no matter what you named your columns:

Linear state typeSay So status
CompletedShipped
CanceledDeclined
StartedIn progress
Backlog / UnstartedNew

Linear is on Pro. See all integrations.

Why an admin key?

No OAuth app or marketplace listing is involved — you just paste a personal API key. To sync issue status back to Say So, we register an Issue webhook on your workspace using that key, and Linear only lets workspace admins create webhooks.

  • Admin key — full two-way sync, set up automatically.
  • Non-admin key — creating Linear issues and pushing status from Say So still work; only the inbound status sync is unavailable.

Ready in about a minute.

Create a project, copy your snippet, paste it in. Need Slack or webhooks? See integrations.