Taggor

Draft · Features

What Taggor does today

Every line below is built, and has a page of the docs or a tested path behind it. Nothing here is a plan.

01

Capabilities

Fig. 1 · Capabilities, each with the page that explains it
CapabilityWhat it doesMore
SDKYour agent writes a component in TypeScript with a typed contract: props, events, slots and the data it may collect.Docs
Check on your machineThe publish gates (types, size, security) run locally, with no network, before anything is sent.Docs
StylesCSS, SCSS and Tailwind (configured in CSS only), scoped to the component.Docs
AccessibilityKeyboard and screen-reader behavior inside the Shadow DOM, including form fields, is tested on the templates.Docs
Build in a sandboxSource is built in an isolated container, apart from your machine and from Taggor's own servers.Docs
Immutable versionsEach publish is a new version addressed by its content. A version never changes; a change is a new version.Docs
ChannelsA channel (staging, production) points at one version. Promote moves it; the sites that use the channel follow.Docs
RollbackPoint a channel back at the previous version from the CLI or the portal. Nothing is rebuilt.Docs
Private previewEach build can be seen at a private, temporary link before it reaches a channel.Docs
One snippet per siteOne async script tag per site loads a small loader that fetches only the components that site uses.Docs
Allowed domainsA site lists the domains where its components mount. It stops a copied snippet from working elsewhere; it is not access control.Docs
Values per siteThe same component can carry different default values on each site, set without a new version.Docs
Collected dataA component declares collections in its manifest; submissions are validated against the schema, checked for spam and stored for you.Docs
ConsentWhen a component declares personal data, it shows Taggor's consent box and each submission carries a proof of what the visitor accepted.Docs
ExportSubmissions are listed in the portal and exported as CSV or NDJSON from the portal and the CLI.Docs
PortalSites, components, versions, previews, props and promote or rollback, without the command line.
Made for agentsllms.txt, an agent setup page, and a skill for coding agents.
Hosts and browsersComponents run in a matrix of plain HTML, a strict Content-Security-Policy, Next.js, a React single-page app and WordPress, in Chromium, Firefox and WebKit.

02

Try it

Three real components, one at a time. Move the controls: the component and the code for your framework change together.

Demo
<contact-form>
View

Props

Accent
Corners
contact-form: a form that stores leads with the visitor's consent. A picture of the real component, drawn from its own code. The controls change the picture and the code beside it; nothing here is sent or stored.

Generated from the component's manifest and the values above.

Code for

index.html

<contact-form
  headline="Ask about your site"
  privacy-policy="https://example.com/privacy"
></contact-form>

<script>
  const element = document.querySelector("contact-form");
  element.addEventListener("contact-form:submitted", (event) => {
    console.log(event.detail);
  });
</script>
  • Add the site's Taggor script once; the component appears when it loads.

taggor-component.js

const element = document.createElement("contact-form");
element.setAttribute("headline", "Ask about your site");
element.setAttribute("privacy-policy", "https://example.com/privacy");
element.addEventListener("contact-form:submitted", (event) => {
  console.log(event.detail);
});
document.body.append(element);
  • Add the site's Taggor script once; the component appears when it loads.

ContactForm.tsx

import { useEffect, useRef } from "react";

export function ContactForm() {
  const ref = useRef<HTMLElement>(null);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;
    const onSubmitted = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    element.addEventListener("contact-form:submitted", onSubmitted);
    return () => {
      element.removeEventListener("contact-form:submitted", onSubmitted);
    };
  }, []);

  return <contact-form
      ref={ref}
      headline="Ask about your site"
      privacy-policy="https://example.com/privacy"
    />;
}
  • Add the site's Taggor script once; the component appears when it loads.
  • React 18 sends every prop of a custom element as a text attribute.
  • The event is heard with addEventListener, inside an effect.

ContactForm.tsx

import { useEffect, useRef } from "react";

export function ContactForm() {
  const ref = useRef<HTMLElement>(null);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;
    const onSubmitted = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    element.addEventListener("contact-form:submitted", onSubmitted);
    return () => {
      element.removeEventListener("contact-form:submitted", onSubmitted);
    };
  }, []);

  return <contact-form
      ref={ref}
      headline="Ask about your site"
      privacy-policy="https://example.com/privacy"
    />;
}
  • Add the site's Taggor script once; the component appears when it loads.
  • React 19 sets a property when the element already has it, an attribute otherwise.
  • The event is heard with addEventListener, inside an effect.

src/ContactForm.vue

<script setup lang="ts">
function onSubmitted(event: Event) {
  console.log((event as CustomEvent).detail);
}
</script>

<template>
  <contact-form
    headline="Ask about your site"
    privacy-policy="https://example.com/privacy"
    @contact-form:submitted="onSubmitted"
  />
</template>

vite.config.ts

import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag === "contact-form",
        },
      },
    }),
  ],
});
  • Add the site's Taggor script once; the component appears when it loads.
  • vite.config.ts tells Vue that this tag is not a Vue component.

src/app/contact-form.component.ts

import { afterNextRender, Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, ElementRef, inject } from "@angular/core";

@Component({
  selector: "app-contact-form",
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <contact-form
      headline="Ask about your site"
      privacy-policy="https://example.com/privacy"
    ></contact-form>
  `,
})
export class ContactFormComponent {
  constructor() {
    const host = inject<ElementRef<HTMLElement>>(ElementRef);
    const element = () => host.nativeElement.querySelector("contact-form");
    const onSubmitted = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    afterNextRender(() => element()?.addEventListener("contact-form:submitted", onSubmitted));
    inject(DestroyRef).onDestroy(() => element()?.removeEventListener("contact-form:submitted", onSubmitted));
  }
}
  • Add the site's Taggor script once; the component appears when it loads.
  • The component declares CUSTOM_ELEMENTS_SCHEMA.
  • The event is heard with addEventListener, inside an effect.

src/TaggorComponent.svelte

<script>
  function onSubmitted(event) {
    console.log(event.detail);
  }
</script>

<contact-form
  headline="Ask about your site"
  privacy-policy="https://example.com/privacy"
  oncontact-form:submitted={onSubmitted}
></contact-form>
  • Add the site's Taggor script once; the component appears when it loads.

How an agent gets a component like this onto a site:

taggor checktaggor publish

The CSS variables the component exposes, set the way a customer's page would.

styles.css

contact-form {
  --cf-accent: #0a7d5a;
  --cf-radius: 6px;
}

What this form collects

Each submission becomes one row in your workspace, with the consent the visitor gave.

Collection: contacts
FieldTypeRule
namestringrequired
emailemailrequired
messagestringoptional

Declared as personal data: the form shows a consent box and keeps its proof.

A row, as it is stored

{
  "name": "Ada Example",
  "email": "ada@example.com",
  "message": "Can the form email me every new lead?"
}

Made-up example data (reserved example.com address). Nothing from this page is stored. This panel is a description: the picture sends nothing.

<quote-wizard>
View

Props

Accent
Corners
One per line, as: name, price. The picture shows three.
Currency
quote-wizard: four steps, a live estimate and a quote request. A picture of the real component, drawn from its own code. The controls change the picture and the code beside it; nothing here is sent or stored.

Generated from the component's manifest and the values above.

Code for

index.html

<quote-wizard currency="USD" locale="en"></quote-wizard>

<script>
  const element = document.querySelector("quote-wizard");
  element.addEventListener("quote-wizard:step", (event) => {
    console.log(event.detail);
  });
  element.addEventListener("quote-wizard:submitted", (event) => {
    console.log(event.detail);
  });
</script>
  • Add the site's Taggor script once; the component appears when it loads.

taggor-component.js

const element = document.createElement("quote-wizard");
element.setAttribute("currency", "USD");
element.setAttribute("locale", "en");
element.addEventListener("quote-wizard:step", (event) => {
  console.log(event.detail);
});
element.addEventListener("quote-wizard:submitted", (event) => {
  console.log(event.detail);
});
document.body.append(element);
  • Add the site's Taggor script once; the component appears when it loads.

QuoteWizard.tsx

import { useEffect, useRef } from "react";

export function QuoteWizard() {
  const ref = useRef<HTMLElement>(null);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;
    const onStep = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    element.addEventListener("quote-wizard:step", onStep);
    const onSubmitted = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    element.addEventListener("quote-wizard:submitted", onSubmitted);
    return () => {
      element.removeEventListener("quote-wizard:step", onStep);
      element.removeEventListener("quote-wizard:submitted", onSubmitted);
    };
  }, []);

  return <quote-wizard ref={ref} currency="USD" locale="en" />;
}
  • Add the site's Taggor script once; the component appears when it loads.
  • React 18 sends every prop of a custom element as a text attribute.
  • The event is heard with addEventListener, inside an effect.

QuoteWizard.tsx

import { useEffect, useRef } from "react";

export function QuoteWizard() {
  const ref = useRef<HTMLElement>(null);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;
    const onStep = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    element.addEventListener("quote-wizard:step", onStep);
    const onSubmitted = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    element.addEventListener("quote-wizard:submitted", onSubmitted);
    return () => {
      element.removeEventListener("quote-wizard:step", onStep);
      element.removeEventListener("quote-wizard:submitted", onSubmitted);
    };
  }, []);

  return <quote-wizard ref={ref} currency="USD" locale="en" />;
}
  • Add the site's Taggor script once; the component appears when it loads.
  • React 19 sets a property when the element already has it, an attribute otherwise.
  • The event is heard with addEventListener, inside an effect.

src/QuoteWizard.vue

<script setup lang="ts">
function onStep(event: Event) {
  console.log((event as CustomEvent).detail);
}

function onSubmitted(event: Event) {
  console.log((event as CustomEvent).detail);
}
</script>

<template>
  <quote-wizard
    currency="USD"
    locale="en"
    @quote-wizard:step="onStep"
    @quote-wizard:submitted="onSubmitted"
  />
</template>

vite.config.ts

import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag === "quote-wizard",
        },
      },
    }),
  ],
});
  • Add the site's Taggor script once; the component appears when it loads.
  • vite.config.ts tells Vue that this tag is not a Vue component.

src/app/quote-wizard.component.ts

import { afterNextRender, Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, ElementRef, inject } from "@angular/core";

@Component({
  selector: "app-quote-wizard",
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <quote-wizard currency="USD" locale="en"></quote-wizard>
  `,
})
export class QuoteWizardComponent {
  constructor() {
    const host = inject<ElementRef<HTMLElement>>(ElementRef);
    const element = () => host.nativeElement.querySelector("quote-wizard");
    const onStep = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    afterNextRender(() => element()?.addEventListener("quote-wizard:step", onStep));
    inject(DestroyRef).onDestroy(() => element()?.removeEventListener("quote-wizard:step", onStep));
    const onSubmitted = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    afterNextRender(() => element()?.addEventListener("quote-wizard:submitted", onSubmitted));
    inject(DestroyRef).onDestroy(() => element()?.removeEventListener("quote-wizard:submitted", onSubmitted));
  }
}
  • Add the site's Taggor script once; the component appears when it loads.
  • The component declares CUSTOM_ELEMENTS_SCHEMA.
  • The event is heard with addEventListener, inside an effect.

src/TaggorComponent.svelte

<script>
  function onStep(event) {
    console.log(event.detail);
  }

  function onSubmitted(event) {
    console.log(event.detail);
  }
</script>

<quote-wizard
  currency="USD"
  locale="en"
  onquote-wizard:step={onStep}
  onquote-wizard:submitted={onSubmitted}
></quote-wizard>
  • Add the site's Taggor script once; the component appears when it loads.

How an agent gets a component like this onto a site:

taggor checktaggor publish

The CSS variables the component exposes, set the way a customer's page would.

styles.css

quote-wizard {
  --qw-radius: 10px;
}
<quiz-result>
View

Props

Accent
Corners
The question first, then one answer per line.
quiz-result: one question per screen and a result chosen by the answers. A picture of the real component, drawn from its own code. The controls change the picture and the code beside it; nothing here is sent or stored.

Generated from the component's manifest and the values above.

Code for

index.html

<quiz-result locale="en"></quiz-result>

<script>
  const element = document.querySelector("quiz-result");
  element.addEventListener("quiz-result:answered", (event) => {
    console.log(event.detail);
  });
  element.addEventListener("quiz-result:completed", (event) => {
    console.log(event.detail);
  });
</script>
  • Add the site's Taggor script once; the component appears when it loads.

taggor-component.js

const element = document.createElement("quiz-result");
element.setAttribute("locale", "en");
element.addEventListener("quiz-result:answered", (event) => {
  console.log(event.detail);
});
element.addEventListener("quiz-result:completed", (event) => {
  console.log(event.detail);
});
document.body.append(element);
  • Add the site's Taggor script once; the component appears when it loads.

QuizResult.tsx

import { useEffect, useRef } from "react";

export function QuizResult() {
  const ref = useRef<HTMLElement>(null);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;
    const onAnswered = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    element.addEventListener("quiz-result:answered", onAnswered);
    const onCompleted = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    element.addEventListener("quiz-result:completed", onCompleted);
    return () => {
      element.removeEventListener("quiz-result:answered", onAnswered);
      element.removeEventListener("quiz-result:completed", onCompleted);
    };
  }, []);

  return <quiz-result ref={ref} locale="en" />;
}
  • Add the site's Taggor script once; the component appears when it loads.
  • React 18 sends every prop of a custom element as a text attribute.
  • The event is heard with addEventListener, inside an effect.

QuizResult.tsx

import { useEffect, useRef } from "react";

export function QuizResult() {
  const ref = useRef<HTMLElement>(null);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;
    const onAnswered = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    element.addEventListener("quiz-result:answered", onAnswered);
    const onCompleted = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    element.addEventListener("quiz-result:completed", onCompleted);
    return () => {
      element.removeEventListener("quiz-result:answered", onAnswered);
      element.removeEventListener("quiz-result:completed", onCompleted);
    };
  }, []);

  return <quiz-result ref={ref} locale="en" />;
}
  • Add the site's Taggor script once; the component appears when it loads.
  • React 19 sets a property when the element already has it, an attribute otherwise.
  • The event is heard with addEventListener, inside an effect.

src/QuizResult.vue

<script setup lang="ts">
function onAnswered(event: Event) {
  console.log((event as CustomEvent).detail);
}

function onCompleted(event: Event) {
  console.log((event as CustomEvent).detail);
}
</script>

<template>
  <quiz-result
    locale="en"
    @quiz-result:answered="onAnswered"
    @quiz-result:completed="onCompleted"
  />
</template>

vite.config.ts

import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag === "quiz-result",
        },
      },
    }),
  ],
});
  • Add the site's Taggor script once; the component appears when it loads.
  • vite.config.ts tells Vue that this tag is not a Vue component.

src/app/quiz-result.component.ts

import { afterNextRender, Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, ElementRef, inject } from "@angular/core";

@Component({
  selector: "app-quiz-result",
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <quiz-result locale="en"></quiz-result>
  `,
})
export class QuizResultComponent {
  constructor() {
    const host = inject<ElementRef<HTMLElement>>(ElementRef);
    const element = () => host.nativeElement.querySelector("quiz-result");
    const onAnswered = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    afterNextRender(() => element()?.addEventListener("quiz-result:answered", onAnswered));
    inject(DestroyRef).onDestroy(() => element()?.removeEventListener("quiz-result:answered", onAnswered));
    const onCompleted = (event: Event) => {
      console.log((event as CustomEvent).detail);
    };
    afterNextRender(() => element()?.addEventListener("quiz-result:completed", onCompleted));
    inject(DestroyRef).onDestroy(() => element()?.removeEventListener("quiz-result:completed", onCompleted));
  }
}
  • Add the site's Taggor script once; the component appears when it loads.
  • The component declares CUSTOM_ELEMENTS_SCHEMA.
  • The event is heard with addEventListener, inside an effect.

src/TaggorComponent.svelte

<script>
  function onAnswered(event) {
    console.log(event.detail);
  }

  function onCompleted(event) {
    console.log(event.detail);
  }
</script>

<quiz-result
  locale="en"
  onquiz-result:answered={onAnswered}
  onquiz-result:completed={onCompleted}
></quiz-result>
  • Add the site's Taggor script once; the component appears when it loads.

How an agent gets a component like this onto a site:

taggor checktaggor publish

The CSS variables the component exposes, set the way a customer's page would.

styles.css

quiz-result {
  --qz-radius: 12px;
}

03

Not on this list

Plugins for specific platforms and billing are not part of this list. A capability appears here when it is built.